感覺自己搜索能力還是不夠,前些日子記得有個地方出過個題,不會做,一查解題報告發現跟個叫什麼Dancing Links的玩意有關,於是就去查了查,然後發現這玩意似乎有點碉……剛剛AC掉模板題,做個記錄…
Dancing Links,簡稱DLX,用於給矩陣精確匹配問題的DFS過程大幅度剪枝,甚至具有自動剪枝的作用。所謂矩陣精確匹配問題,就是你有一個矩陣,裏面元素不是0就是1,然後希望你選出一些行,使得這些行的總集中,每一列都有且只有一個1. 原來樸素的DFS就是枚舉列,然後把這一列刪掉——問題就出在這裏,“刪掉”一列需要很高的時間複雜度,所以往往我們會只把它標記成“刪除了”,但是接下來的搜索中,明明矩陣應該越來越小,我們卻花着最初時的代價搜索矩陣,明顯很虧。
DLX厲害的地方在於它的數據結構:它用一個十字鏈表結點來表示每一個矩陣中爲1的地方,然後它利用雙向鏈表方便的刪除以及刪除恢復操作:刪除x時只需 R[ L[x] ]= R[x] , L[ R[x] ] = L[x] (我的左邊的右邊是我的右邊,反之亦然), x就找不着了,然後恢復的時候,只需要兩句:L[ R[x] ] = x, R[ L[x] ] =x 就又能找着x了。這個對於回溯是非常有用的。
而且DLX解決問題的單一性使它可以作爲模板直接使用,前期建模以後,直接構建矩陣然後去DFS就搞定了……所以最麻煩的地方恐怕還是建模。
不管怎樣,先放一個剛剛弄的模板吧,只過了POJ 3740, 不知道好不好使。另外momodi的關於DLX的文章非常好懂,代碼也很規範,值得一讀~
#include<stdio.h>
#include<string.h>
#include<math.h>
using namespace std;
const int INF=2<<25;
const int MAXNUM=6010;
int n,m;
int u[MAXNUM], d[MAXNUM], l[MAXNUM], r[MAXNUM];//上下左右
int s[MAXNUM], col[MAXNUM];//s[i]:第i列有幾個節點,即有幾個1,col[i]能告訴你i這個節點在第幾列
int head;//總表頭,其實就是0號節點
int p_nodes;//目前用了多少節點
void del(int c)//刪掉列c以及列c中A[i][j]=1的所有行
{
l[ r[c] ] = l[c];
r[ l[c] ] = r[c];
for(int i=d[c]; i!=c; i=d[i])
{
for(int j=r[i]; j!=i; j=r[j])
{
u[ d[j] ] = u[j];
d[ u[j] ] = d[j];
s[ col[j] ] --;
}
}
return;
}
void resume(int c)//恢復上面的操作
{
for(int i=u[c]; i!=c; i=u[i])
{
for(int j=l[i]; j!=i; j=l[j])
{
s[ col[j] ] ++;
d[ u[j] ]=j;
u[ d[j] ]=j;
}
}
r[ l[c] ] =c;
l[ r[c] ] = c;
return;
}
bool DFS(int depth)
{
////TEST
//cout<<depth<<endl;
if(r[head] == head)
return true;//矩陣被刪乾淨了
int min1=INF, now;//挑一個1數最少列的先刪
for(int t=r[head]; t!=head; t=r[t])
{
if(s[t] <=min1 )
{
min1=s[t];
now=t;
}
}
del(now);//刪掉此列
int i, j;
for(i=d[now]; i!=now; i=d[i])
{//枚舉這一列每個1由哪行來貢獻,這行即爲暫時性的答案,如果需記錄答案,此時ans[depth]=i
for(j=r[i]; j!=i; j=r[j])
{
del( col[j] );//選取了第i行,就要把本行所有的1所在列都刪掉
}
bool tmp=DFS(depth+1);
for(j=l[i]; j!=i; j=l[j])
{
resume(col[j]);
}
if(tmp==true)
return true;
}
resume(now);
return false;
}
void init()
{
memset(u,0,sizeof(u));
memset(d,0,sizeof(d));
memset(l,0,sizeof(l));
memset(r,0,sizeof(r));
memset(s,0,sizeof(s));
memset(col,0,sizeof(col));
head=0;
p_nodes=0;
}
void InitLinks()//注意:該鏈表兩個方向都是循環的,最上面的上面指最下面
{
int i,j;
r[head]=1;
l[head]=m;
for(i=1;i<=m;i++)//製作列的表頭,使用結點1~m
{
l[i]=i-1;
r[i]=i+1;
if(i==m)
r[i]=head;
u[i]=i;
d[i]=i;
s[i]=0;
col[i]=i;
}
p_nodes=m;
for(i=1;i<=n;i++)
{
int row_first1=-1;//看看這一行是不是已經有第一個1了
for(j=1;j<=m;j++)
{
int k;
scanf("%d", &k);
if(k==1)
{
p_nodes++;
s[j] ++;
u[p_nodes] = u[j];
d[ u[j] ] = p_nodes;
u[j] = p_nodes;
d[p_nodes] = j;
col[p_nodes] = j;//和列的關係處理完畢
if(row_first1==-1)
{
l[p_nodes] = r[p_nodes] = p_nodes;
row_first1=p_nodes;
}
else
{
l[p_nodes] = l[row_first1];
r[ l[row_first1] ] = p_nodes;
r[p_nodes] = row_first1;
l[ row_first1 ]=p_nodes;//和行的關係處理完畢
}
}
}
}
}
int main()
{
while(scanf("%d %d", &n, &m) ==2)
{
init();
InitLinks();
bool ok=true;
for(int i=1;i<=m;i++)
{
if(s[i]==0)
{
ok=false;
break;
}
}
if(ok)
ok=DFS(1);
if(ok)
printf("Yes, I found it\n");
else
printf("It is impossible\n");
}
return 0;
}