(从网上某神犇处转载的)
比如2006年11月的月赛:
ace.delos.com/NOV06
然后就傻掉了……
其它的类似这样搞就行了囧……

posted @ 2011-10-04 23:36 Mato_No1 阅读(2178) | 评论 (3)编辑 收藏

本沙茶自从NOI2011结束以来,陷入危机,具体表现为:编程速度变慢;想题目的时候容易想偏;编程时比较不专注,容易想到别的东西上去;容易受到杂念的干扰。这次危机是本沙茶至今为止遇到的最严重的一次危机,到了9月中下旬,这次危机发展到极限,几乎到了绝望的边缘。因此,本沙茶在长达1个多月的时间里,没有什么实质性的进展,也没有在这个空间里作总结。

在十一假期的前四天里,本沙茶努力调整状态,终于在10月4日(也就是今天),正式摆脱了危机,恢复到了正常状态。以后,本沙茶会继续在这里作总结,同时争取尽可能大的提高,在本赛季的各项比赛中竭尽全力,争取好成绩。

感谢BZOJ对本沙茶在摆脱危机过程中的帮助。
Orz WJMZBMR等神犇

posted @ 2011-10-04 23:28 Mato_No1| 编辑 收藏

CF 挂0……
掉DIV2……
真不知道世界上还有木有比我还弱的……

真的陷入危机了么?
——————————————————————————————————————————
不,我不能让自己陷入危机!!!

posted @ 2011-09-04 14:53 Mato_No1 阅读(628) | 评论 (0)编辑 收藏

此帖用于记录NOI2011团体对抗赛的各版本(代码)更新情况。
————————————————————————————————————————————————————
【2011年7月28日】
目前代码框架(Program0.cpp)已经完成。
NOI2011团体对抗赛安徽队的代码名称初步定为:Docx
————————————————————————————————————————————————————
【2011年7月29日】
Docx I已经完成,代码长度(包括注释)为5368Bytes
版本说明:
(1)对优势分数G的计算为G2.1版本;
(2)启发函数还比较弱;
目前由于测评机问题,暂时无法测试,只有看晚上的练习赛了。
————————————————————————————————————————————————————
【2011年7月30日】
今天秃然发现了Docx I的一个严重错误:在判定是否有炸弹的时候把true和false弄反了,结果导致炸弹根本无法使用。
现在改掉了之后(仍然命名为Docx I),TEST结果仍然不是很理想,只能虐别人的一些初等版本,高等版本(比如那个"I Hate Zerg"的版本2)根本木有办法。
目前正在改进启发函数,争取在明天下午之前出Docx II。
————————————————————————————————————————————————————
【2011年8月1日】
真惊险!最后时刻终于找到了问题所在,并且把Docx II经最后一步改进得到的版本命名为Docx III,提交到排位赛。
最后结果……还行,第3名……只是严重怀疑有强省故意放水……
大概明天正式分组就能出来了……
————————————————————————————————————————————————————
【2011年8月2日】
分组结果已经出来,安徽队的形势还是比较有利的……

好了,在8月10日下午以前都不用再管团体对抗赛了……全力冲刺个人竞赛……
————————————————————————————————————————————————————
【2011年8月10日】
团体对抗赛最后冲刺。
今天把几个地方的系数改了一下,一些看起来很傻的决策也得到了修正。不过晚上练习赛的结果仍然很不理想……
算了,明天1/4决赛等着被血洗吧囧……

现在还有一点时间,乱搞一下吧囧……
————————————————————————————————————————————————————
【2011年8月11日】
最终结果:八强,1/4决赛中被湖南血洗

显然是一个预想之中的结果……还是应验了那句话:团体对抗赛碰到湖南就是找死……
这次,本沙茶真的已经尽力了……
明年再来吧……

posted @ 2011-07-28 22:08 Mato_No1 阅读(1128) | 评论 (1)编辑 收藏

在图DFS的过程中,给每个点设立两个附加值dfn和low。dfn[i]表示点i被发现(变灰)的时间,也就是“发现次序”。而low[i]表示i能够到达的dfn值最小的i的祖先结点或i本身的dfn值,即low[i]=min{dfn[i], dfn[j], low[k]}(其中j为满足以下条件的点:图中存在边<i, j>且这条边在遍历到的时候j在栈中(这个栈的具体说明见下);k为遍历树中i的某个子结点,)。

Tarjan算法就是在图DFS过程中,得到每个点的dfn值和low值,并且设立一个栈,点变灰的时候入栈,在某个点i的low值真正确定后(容易发现,一个点只有变黑了,它的low值才真正确定),若low[i]=dfn[i],则将栈中点i及其上面的所有点全部弹出(这些点一定是以i为根的子树中的结点,因为它们比i后入栈,却比i先出栈),这些点组成一个强连通分支,这样在图DFS遍历完后,即可得到所有的强连通分支。

下面证明结点i变黑时,若low[i]=dfn[i],则
i及其所有未出栈的后代组成一个强连通分支。
证明这个需要分两步:
【1】i的所有未出栈的后代与i处于同一个强连通分支。
设j是i的某个后代且j在i变黑时尚未出栈。显然i可达j,因此只需证明j可达i。
在遍历树中,从i到j路径上除i外的所有结点的low值都小于dfn值(否则,设这条路径上存在结点k,k≠i,low[k]=dfn[k]。因为k比i先变黑,所以在k变黑时,其所有后代就会出栈,这与j在i变黑时仍未出栈矛盾,故这样的结点k不存在)。设dfn[x]=low[j],则根据low的定义以及low[j]<dfn[j]得,必然是j的祖先且从j先往下再经过一条逆向边可以到达点x,并且,x不可能是i的祖先(若x是i的祖先,则i先往下再经过一条逆向边可达x,因此dfn[x]>=low[i],又因为dfn[x]<dfn[i],所以low[i]<dfn[i],这与low[i]=dfn[i]矛盾),也就是x位于路径i->j上且x≠j。若x=i,则j可达i,结束,否则先从j到达x,再将x当成j,继续往上……这样最终必然能到达点i。因此j可达i,即j与i处于同一个强连通分支。
【2】不是i的未出栈的后代不与i处于同一个强连通分支。
设j不是i的未出栈的后代,则j有两种情况:
(1)j是i的后代,且在i变黑前已经出栈:
此时,i->j路径上必然存在一个点k,满足k≠i,且dfn[k]=low[k](这样的k可能有多个,此时取最深的那个为k),当k变黑时j出栈。这时,由与【1】类似的推导过程可得,j不断往上到达的最上层祖先结点也只能是k,到不了i,故j不可达i,也即j与i不处于同一个强连通分支;
(2)j不是i的后代,考虑当i变黑时,j的颜色:
<1>白色:此时,若i可达j,则j在i变黑前,就会成为i的后代,这与j不是i的后代矛盾,故i一定不可达j,也即j与i不处于同一个强连通分支;
<2>灰色:若j为灰色,则j一定是i的祖先,若i可达j,根据low的定义可得,必有low[i]<=dfn[j]<dfn[i],这与low[i]=dfn[i]矛盾,故i不可达j,也即j与i不处于同一个强连通分支;
<3>黑色:若j不是i的后代,且比i先变黑,则必然是在i变灰前j已经变黑。这时,若j可达i,则i会成为j的后代,也即j是i的祖先,这样在i变黑时,j应为灰色,矛盾,故j不可达i,也即j与i不处于同一个强连通分支;
综上可得,i、j一定不处于同一个强连通分支。故原命题得证,也就证明了Tarjan算法是可以求出有向图的所有强连通分支的。

时间复杂度分析:由于每个点都要入栈一次,出栈一次,每条边都要被访问一次,所以总时间复杂度为O(N+M)。

写代码时注意事项:
(1)要用人工栈实现DFS,不可用递归,防止爆栈;另外注意不要将这个人工栈与上面说到的“栈”弄混,本沙茶下面的代码中,用于搜索、回溯的人工栈为stk0,而用于找强分支的栈为stk;
(2)注意边界情况;
(3)注意在出栈时要将V值设为2;

(4)注意计算low[i]时,需要考虑非i的祖先,但仍在栈中的结点,它们也被视为i的“祖先”。

核心代码:
void solve()
{
    re(i, n) {V[i] 
= vst[i] = 0; st[i] = E[i].next;}
    
int x, y, x0, tp, tp0, ord = 0, No = 0;
    
bool fd;
    re(i, n) 
if (!V[i]) {
        stk[tp 
= 0= stk0[tp0 = 0= i; vst[i] = 1; low[i] = dfn[i] = ++ord; V[i] = 1;
        
while (tp0 >= 0) {
            x 
= stk0[tp0]; fd = 0;
            
for (int p=st[x]; p != x; p=E[p].next) {
                y 
= E[p].b;
                
if (!V[y]) {
                    fd 
= 1; stk[++tp] = stk0[++tp0] = y; vst[y] = 1; low[y] = dfn[y] = ++ord; V[y] = 1; st[x] = E[p].next; break;
                } 
else if (vst[y] && dfn[y] < low[x]) low[x] = dfn[y];
            }
            
if (!fd) {
                V[x] 
= 2;
                
if (low[x] == dfn[x]) {while (stk[tp] != x) {vst[stk[tp]] = 0; w[stk[tp--]] = No;} vst[x] = 0; w[stk[tp--]] = No++;}
                
if (tp0) {x0 = stk0[tp0 - 1]; if (low[x] < low[x0]) low[x0] = low[x];} tp0--;
            }
        }
    }
    re(i, n) {reslen[i] 
= 0for (int p=E[i].next; p != i; p=E[p].next) if (w[i] == w[E[p].b]) reslen[i]++;}
}


posted @ 2011-07-28 20:57 Mato_No1 阅读(1757) | 评论 (0)编辑 收藏

【背景(神犇不要鄙视)】
本沙茶今年AHOI的时候,遇到裸的最佳匹配的题,竟然把KM算法搞忘了,幸亏是WJMZBMR神犇保佑,临时乱弄一通,想起来了……这MS反映出了本沙茶以前在看某些经典算法的时候看得不深,木有理解透彻……
前几天又遇到一道最佳匹配的题,发现KM算法竟然又忘了……米办法,只有把这个搞死人的算法的具体过程重新看了一遍,终于懂了……
【KM算法及其具体过程】
(1)可行点标:每个点有一个标号,记lx[i]为X方点i的标号,ly[j]为Y方点j的标号。如果对于图中的任意边(i, j, W)都有lx[i]+ly[j]>=W,则这一组点标是可行的。特别地,对于lx[i]+ly[j]=W的边(i, j, W),称为可行边
(2)KM算法的核心思想就是通过修改某些点的标号(但要满足点标始终是可行的),不断增加图中的可行边总数,直到图中存在仅由可行边组成的完全匹配为止,此时这个匹配一定是最佳的(因为由可行点标的的定义,图中的任意一个完全匹配,其边权总和均不大于所有点的标号之和,而仅由可行边组成的完全匹配的边权总和等于所有点的标号之和,故这个匹配是最佳的)。一开始,求出每个点的初始标号:lx[i]=max{e.W|e.x=i}(即每个X方点的初始标号为与这个X方点相关联的权值最大的边的权值),ly[j]=0(即每个Y方点的初始标号为0)。这个初始点标显然是可行的,并且,与任意一个X方点关联的边中至少有一条可行边
(3)然后,从每个X方点开始DFS增广。DFS增广的过程与最大匹配的Hungary算法基本相同,只是要注意两点:一是只找可行边,二是要把搜索过程中遍历到的X方点全部记下来(可以用vst搞一下),以进行后面的修改;
(4)增广的结果有两种:若成功(找到了增广轨),则该点增广完成,进入下一个点的增广。若失败(没有找到增广轨),则需要改变一些点的标号,使得图中可行边的数量增加。方法为:将所有在增广轨中(就是在增广过程中遍历到)的X方点的标号全部减去一个常数d,所有在增广轨中的Y方点的标号全部加上一个常数d,则对于图中的任意一条边(i, j, W)(i为X方点,j为Y方点):
<1>i和j都在增广轨中:此时边(i, j)的(lx[i]+ly[j])值不变,也就是这条边的可行性不变(原来是可行边则现在仍是,原来不是则现在仍不是);
<2>i在增广轨中而j不在:此时边(i, j)的(lx[i]+ly[j])的值减少了d,也就是原来这条边不是可行边(否则j就会被遍历到了),而现在可能是;
<3>j在增广轨中而i不在:此时边(i, j)的(lx[i]+ly[j])的值增加了d,也就是原来这条边不是可行边(若这条边是可行边,则在遍历到j时会紧接着执行DFS(i),此时i就会被遍历到),现在仍不是;
<4>i和j都不在增广轨中:此时边(i, j)的(lx[i]+ly[j])值不变,也就是这条边的可行性不变。
这样,在进行了这一步修改操作后,图中原来的可行边仍可行,而原来不可行的边现在则可能变为可行边。那么d的值应取多少?显然,整个点标不能失去可行性,也就是对于上述的第<2>类边,其lx[i]+ly[j]>=W这一性质不能被改变,故取所有第<2>类边的(lx[i]+ly[j]-W)的最小值作为d值即可。这样一方面可以保证点标的可行性,另一方面,经过这一步后,图中至少会增加一条可行边。
(5)修改后,继续对这个X方点DFS增广,若还失败则继续修改,直到成功为止;

下面分析整个算法的时间复杂度:每次修改后,图中至少会增加一条可行边,故最多增广M次、修改M次就可以找到仅由可行边组成的完全匹配(除非图中不存在完全匹配,这个可以通过预处理得到),故整个算法的时间复杂度为O(M * (N + 一次修改点标的时间))。而一次修改点标的时间取决于计算d值的时间,如果暴力枚举计算,这一步的时间为O(M),优化:可以对每个Y方点设立一个slk值,表示在DFS增广过程中,所有搜到的与该Y方点关联的边的(lx+ly-W)的最小值(这样的边的X方点必然在增广轨中)。每次DFS增广前,将所有Y方点的slk值设为+∞,若增广失败,则取所有不在增广轨中的Y方点的slk值的最小值为d值。这样一次修改点标的时间降为O(N),总时间复杂度降为O(NM)。

需要注意的一点是,在增广过程中需要记下每个X、Y方点是否被遍历到,即fx[i]、fy[j]。因此,在每次增广前(不是对每个X方点增广前)就要将所有fx和fy值清空。

代码:

void init_d()
{
    re(i, n) E[i].pre 
= E[i].next = i; m = n;
}
void add_edge(int a, int b, int len)
{
    E[m].a 
= a; E[m].b = b; E[m].len = len; E[m].pre = E[a].pre; E[m].next = a; E[a].pre = m; E[E[m].pre].next = m++;
}
inline 
int dist(int x, int y, int x0, int y0)
{
    
return abs(x - x0) + abs(y - y0);
}
bool dfs(int x)
{
    
int y, x0; fx[x] = _FLAG;
    
for (int p=E[x].next; p != x; p=E[p].next) {
        y 
= E[p].b;
        
if (lx[x] + ly[y] > E[p].len) {
            
if (lx[x] + ly[y] - E[p].len < slk[y]) slk[y] = lx[x] + ly[y] - E[p].len;
        } 
else if (fy[y] != _FLAG) {
            fy[y] 
= _FLAG; x0 = z[y];
            
if (x0 == -1 || dfs(x0)) {z[y] = x; return 1;}
        }
    }
    
return 0;
}
void solve()
{
    re(i, n) {
        lx[i] 
= -INF;
        
for (int p=E[i].next; p != i; p=E[p].next) if (E[p].len > lx[i]) lx[i] = E[p].len;
    }
    re(i, n0) {ly[i] 
= 0; z[i] = -1;}
    
int d;
    re(i, n) {
        re(j, n0) slk[i] 
= INF; _FLAG++;
        
while (!dfs(i)) {
            d 
= INF; re(j, n0) if (fy[j] != _FLAG && slk[j] < d) d = slk[j];
            re(j, n) 
if (fx[j] == _FLAG) lx[j] -= d;
            re(j, n0) {slk[j] 
= INF; if (fy[j] == _FLAG) ly[j] += d;}
            _FLAG
++;
        }
    }
    res 
= 0; re(i, n) res += lx[i]; re(i, n0) res += ly[i];
}


 

posted @ 2011-07-23 22:14 Mato_No1 阅读(7544) | 评论 (9)编辑 收藏

这次总算木有再莫名其妙地挂掉了……交了三题,全AC……

【D、E题到现在理解不了,样例是怎么来的?】

【最新消息:已发现D题的数据有问题,详见官网】

posted @ 2011-07-23 02:30 Mato_No1 阅读(349) | 评论 (0)编辑 收藏

【1】IOI2009 hiring(这个在各大OJ上都找不到囧,只能看这里了囧,第11页)
可以发现本题就是求一个比率rate,使得第i个人(如果用的话)工资为rate*Qi,并且还要满足以下两个限制条件:
(1)每人的最低工资限制:第i个人如果用的话,有rate*Qi>=Si,即rate>=Si/Qi;
(2)总开销限制:rate*所有用的人的Q值之和<=W,即所有用的人的Q值之和<=W/rate。
这样,可以先将所有人按照(S/Q)的值递增排序,然后枚举需要用的最后一个人(排序后的,也就是S/Q值最大的那个人),设为i,则总花费最省的做法显然是取rate=Si/Qi。然后根据(2)式得出“所有用的人的Q值之和”的最大值W0=W/rate,其中,第i个人是必须要用的,故将W0值先减去Qi(若W0<Qi,则第i个人不可使用),剩下的问题就变成了在第0~(i-1)个人中(排序后的)选取一些人,使得他们的Q值之和不大于W0,并且选取的人尽可能多。显然这可以用贪心来实现,即选取Q值最小的若干个人。接下来,由于题目中N<=500000,说明需要用数据结构来优化,可是Q的上限只有20000且Q为正整数,因此,线段树是最好的选择。建立一棵表示[1, 20000]的线段树,每个结点存放两个额外的值:sz和sum,分别表示Q值位于该结点代表的区间内的人的总数以及这些人的Q值总和。然后,需要解决上述子问题时,从根结点开始考察结点的sz值,不断往下找即可(这有点像平衡树的找第K小的操作)。
总时间复杂度:O(N * (log20000 + logN))(还有排序的时间)
代码

【2】RQNOJ469
先按照任意一种属性(这里为A)递增排序,然后枚举值i,排序后第1位~第i位的全部给A(看A属性,它们中A属性最大的一定是i),排序后第(i+1)位及以后的,看其B、C两种属性的大小,若B属性更小就看B属性,若C属性更小就看C属性,然后得出两种属性的最大值即可。因此可以得到下面的算法:先排序,然后将所有的毛的B或C属性(哪种更小就看哪种)插入平衡树(这里需要两棵平衡树,一棵存放B属性的值,一棵存放C属性的值),然后递增枚举i(注意i=0的情况不要漏掉),将第i位的B或C属性在平衡树中删除,然后找出两棵平衡树中的最大值即可。
但是需要注意一种特殊情况:所有的毛都看同一个属性,此时按照上面的算法可能求不出最优解,比如:
10 6 5
10 2 8
此时,第1个C属性更小,第2个B属性更小,若第1个看C属性,第2个看B属性,则总和为5+2=7,而如果两个都看B属性则总和为6。此时就需要特判(预先求出三种属性中的最大值),然后再用上面的算法求解,就能保证求出最优解了。
代码

【3】PKU2985
并查集+平衡树基本操作,水题,不解释。
代码

【4】HNOI2011 括号匹配Brackets(目前可以看这个帖子
Splay Tree维护序列问题。对于一段括号序列A[1..len],定义优先级P[0..len]如下:
P[0]=0
P[i]=P[i-1]+1(i>0且A[i]为左括号)
P[i]=P[i-1]-1(i>0且A[i]为右括号)
然后,Splay Tree的每个结点需要记录一个Z值和M值,分别表示该结点代表的括号序列中最后一个元素的优先级和优先级最小的元素的优先级。则可以证明:这段括号序列调整至平衡至少需要改变的括号数目为(-M+K+1) / 2,其中K=Z+((-M+1)/2)*2(注意这里的/是整除),此外由于有swap和invert两个操作,因此需要记录RM、TM、RTM值,分别表示将该括号序列执行swap操作后的序列的M值、执行invert操作后的序列的M值,以及同时执行swap和invert操作后序列的M值。
不过,本题需要严重注意的是:虽然replace操作的标记(代码中的mk0)会覆盖掉swap(代码中的mk1)和invert(代码中的mk2)操作的标记,但是在下放标记的时候,需要对三种标记逐一判断,mk0和mk1、mk2并不是不能共存的!因为有可能先打上mk0标记后再打上mk1或mk2标记。
本题虽然是静态的,但仍然不能使用线段树,因为线段树无法支持整体翻转(rev)操作。
代码

posted @ 2011-07-23 02:24 Mato_No1 阅读(1144) | 评论 (0)编辑 收藏

     摘要: 【原题见这里】很明显的精确覆盖的模型,12个零件,再加上它们经过旋转、翻转后的不同形状,共60种,每种又可以放在不同的位置(只看最左上的那个珠子)一种零件的一种位置就是一行。列(限制)有两种:每个格子只能放一个(55列),以及每个零件(注意是每个零件,不是每种零件)只能用一次(12列),共67列。预先放的那些零件可以看成预先选中的行。然后DLX精确覆盖即可。下面是DLX精确覆盖的几个易疵点:(1)...  阅读全文

posted @ 2011-07-20 23:07 Mato_No1 阅读(1153) | 评论 (2)编辑 收藏

昨天在刷PKU1084的时候,调了很久,发现原来那样实现有缺陷。(原来的实现见这里

在重复覆盖问题中,删去一整列的操作(delcol)是断开该列除了初始结点外的所有结点的左右链(delLR),这样,如果有一些行预先已被选中,则删去这一行(准确来说是对这一行所有结点执行delcol操作),这时就会出现问题,因为在删去这一行的最后一个结点的时候,其左、右链都指向其本身,此时就无法删掉这个结点。解决这一问题的办法是除了在矩阵中引入列头以外,还要引入行头(rowh),并且保证行头不删掉。这样在删掉一整行的时候就不会出问题了。但是,如果这样的话,需要在搜索过程中执行delcol操作前进行特判,保证不删掉行头结点(比如将行头结点的U、D域置为-1),并且,在求启发函数h()值的时候也要防止在行头处出现问题,可以将行头结点的行列号均置为0。

而在精确覆盖问题中,删去一整列的操作是断开结点的上下链而不是左右链,因此在删去一整行时就不需要引入行头结点。

下面是PKU1084的AC代码:
#include <iostream>
#include 
<stdio.h>
using namespace std;
#define re(i, n) for (int i=0; i<n; i++)
#define re1(i, n) for (int i=1; i<=n; i++)
#define re3(i, l, r) for (int i=l; i<=r; i++)
const int MAXN = 60, MAXM = 55, INF = ~0U >> 2;
struct dlnode {
    
int r, c, U, D, L, R;
} d[(MAXN 
+ 1* (MAXM + 1)];
int _n, n, m, nodes, rowh[MAXN + 1], cols[MAXM + 1], B[5][5][4], res;
bool A[MAXN + 1], vst[MAXN];
void init_d()
{
    re3(i, 
0, m) {d[i].U = d[i].D = i; d[i].L = i - 1; d[i].R = i + 1;} d[0].L = m; d[m].R = 0;
    nodes 
= m; re1(i, n) {rowh[i] = ++nodes; d[nodes].L = d[nodes].R = nodes; d[nodes].U = d[nodes].D = -1;}
    re1(i, m) cols[i] 
= 0;
}
void add_node(int r, int c)
{
    cols[c]
++; d[++nodes].r = r; d[nodes].c = c;
    d[nodes].U 
= d[c].U; d[nodes].D = c; d[c].U = nodes; d[d[nodes].U].D = nodes;
    
int rh = rowh[r]; d[nodes].L = d[rh].L; d[nodes].R = rh; d[rh].L = nodes; d[d[nodes].L].R = nodes;
}
void delLR(int x)
{
    d[d[x].L].R 
= d[x].R; d[d[x].R].L = d[x].L;
}
void delUD(int x)
{
    d[d[x].U].D 
= d[x].D; d[d[x].D].U = d[x].U;
}
void resuLR(int x)
{
    d[d[x].L].R 
= d[d[x].R].L = x;
}
void resuUD(int x)
{
    d[d[x].U].D 
= d[d[x].D].U = x;
}
void delcol(int x)
{
    
for (int i=d[x].D; i != x; i=d[i].D) delLR(i);
}
void resucol(int x)
{
    
for (int i=d[x].U; i != x; i=d[i].U) resuLR(i);
}
void prepare()
{
    
int x = 0;
    re(i, _n) {
        re(j, _n) {B[i][j][
0= ++x; B[i][j][1= x + _n; B[i][j][2= x + _n + 1; B[i][j][3= x + _n + _n + 1;}
        x 
+= _n + 1;
    }
    x 
= 0;
    re(i, _n) re(j, _n 
- i) re(k, _n - i) {
        x
++;
        re(t, i
+1) {
            add_node(B[j][k 
+ t][0], x);
            add_node(B[j 
+ t][k][1], x);
            add_node(B[j 
+ t][k + i][2], x);
            add_node(B[j 
+ i][k + t][3], x);
        }
    }
    
int rh;
    re1(i, n) 
if (A[i]) {
        rh 
= rowh[i];
        
for (int j=d[rh].R; j != rh; j=d[j].R) delcol(j);
    }
    res 
= n;
}
int h()
{
    re1(i, m) vst[i] 
= 0;
    
int z = 0;
    
for (int i=d[0].R; i; i=d[i].R) if (!vst[i]) {z++; vst[i] = 1for (int j=d[i].D; j != i; j=d[j].D) for (int k=d[j].R; k != j; k=d[k].R) vst[d[k].c] = 1;}
    
return z;
}
void dfs(int dep)
{
    
int h0 = h(); if (dep + h0 >= res) returnelse if (!h0) {res = dep; return;}
    
int mins = INF, c0; for (int i=d[0].R; i; i=d[i].R) if (!cols[i]) returnelse if (cols[i] < mins) {mins = cols[i]; c0 = i;}
    
for (int i=d[c0].D; i != c0; i=d[i].D) {
        delcol(i); 
for (int j=d[i].R; j != i; j=d[j].R) if (d[j].U != -1) delcol(j);
        dfs(dep 
+ 1);
        
for (int j=d[i].L; j != i; j=d[j].L) if (d[j].U != -1) resucol(j); resucol(i);
    }
}
int main()
{
    
int tests, _n0, x;
    scanf(
"%d"&tests);
    re(testno, tests) {
        scanf(
"%d%d"&_n, &_n0);
        n 
= _n * (_n + 1<< 1; m = 0; re1(i, _n) m += i * i; init_d(); re1(i, n) A[i] = 0;
        re(i, _n0) {scanf(
"%d"&x); A[x] = 1;}
        prepare();
        dfs(
0);
        printf(
"%d\n", res);
    }
    
return 0;
}

posted @ 2011-07-20 12:28 Mato_No1 阅读(1495) | 评论 (0)编辑 收藏

仅列出标题
共12页: First 4 5 6 7 8 9 10 11 12