dancing link
http://www.cnblogs.com/grenet/p/3145800.html
链接给的博客写的很好,比较好懂。
可惜不是c语言...
于是决定自己要建一个模板。
一道裸题:hustoj 1017 exact cover 输入一个矩阵求选哪些行,使得这是一个精确覆盖
AC通道:http://acm.hust.edu.cn/problem/show/1017
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 5 using namespace std; 6 7 const int maxn=1010; 8 const int INF=0x7fffffff; 9 10 typedef int array[maxn]; 11 typedef int Array[maxn*maxn]; 12 13 int n,m,cnt,size; 14 array last,f,d,ans; 15 Array L,R,U,D,c,r,s; 16 17 void build(){ //初始化 18 /*构造矩阵第一行: 19 1.将第一行的两端环状处理。[提示]:第一行的元素的标号就是其列的标号。 20 2.对于第一行的所有元素: 21 连接其左右;标注其行列;清空列中表示的元素个数;清空表示列的上一个元素的d数组。*/ 22 23 L[0]=m,R[m]=0; 24 for(int i=1;i<=m;i++){ 25 L[i]=i-1,R[i-1]=i; 26 c[i]=i;r[i]=0; 27 s[i]=0; 28 d[i]=0; 29 } 30 31 //清空所有行上的表示上一个元素的last数组和表示首元素的f数组 32 for(int i=1;i<=n;i++) last[i]=f[i]=0; 33 } 34 35 void del(int col){ //删列操作 36 /* 37 1.在第一行中删除这个列节点。 38 2.找到每个在这条列上的节点i 39 将节点i所在的行从列表中删除 40 */ 41 42 L[R[col]]=L[col],R[L[col]]=R[col]; //在第一行中删除这个列节点。 43 44 for(int i=D[col];i!=col;i=D[i]) 45 for(int j=R[i];j!=i;j=R[j]) //将节点i所在的行删除干净[因为是环状的,所以可以删掉这行上的所有节点] 46 U[D[j]]=U[j],D[U[j]]=D[j],s[c[j]]--; //这些节点都在列表中不再被查询得到。 47 } 48 49 void add(int col){ //恢复列操作 50 /* 51 1.在第一行中恢复这个列节点。 52 2.找到每个在这条列上的节点i 53 将节点i所在的行有顺序的从列表中恢复 54 */ 55 56 R[L[col]]=col,L[R[col]]=col; //因为删除时其实是间接删除[将其两边元素的指针改变],恢复只需要从它自己出发恢复两边即可。 57 58 for(int i=U[col];i!=col;i=U[i]) 59 for(int j=L[i];j!=i;j=L[j]) //同删除的顺序相反的添加回来[即后删去的先添加],才保证了顺序,不会错 60 U[D[j]]=j,D[U[j]]=j,s[c[j]]++; //同删除的感觉,通过行表找到这些丢失的点,然后从它自己来恢复列表中它们的位置。 61 } 62 63 bool search(int k){ 64 if(R[0]==0){ //第一行中的所有元素都被删除[即列都被标记,完成了精确覆盖]了 65 printf("%d",k); 66 for(int i=1;i<=k;i++) 67 printf(" %d",r[ans[i]]); 68 putchar('\n'); 69 return true; 70 } 71 int Min=INF,C; 72 for(int i=R[0];i;i=R[i]) 73 if(Min>s[i]) Min=s[i],C=i; //优先选择列中元素少的列来处理 74 del(C); 75 for(int i=D[C];i!=C;i=D[i]){ //确定要删掉这列,但是要考虑删掉哪一行[当然是必须要与这一列相交的行] 76 ans[k+1]=i; 77 for(int j=R[i];j!=i/*环状链表的好处,可以循环寻找,找到所有元素*/;j=R[j]) del(c[j]); //当确定删掉i行时,还要删掉所有与其相交的列 78 if(search(k+1)) return true; 79 for(int j=L[i];j!=i;j=L[j]) /*环状链表的另一个好处,可以让添加的顺序与删除的顺序相反,从而达到后删去的先添加的形式*/ 80 add(c[j]); //回溯过程,补充回原来删去的列。 81 } 82 add(C); 83 return false; 84 } 85 86 void link(int row/*行*/,int col/*列*/){ 87 size++; //点的数目++ 88 s[col]++; //所在列的元素个数++ 89 90 /*行操作: 91 如果这一行之前没有元素了,这个元素作为本行的首元素,记录在f[row]中;并记录在表示上一个元素的last[row]中。 92 若所在行之前还有元素,将这个元素与上一个元素连起来,更新last[];并将这个元素的右端与该行的首元素连起来,形成一个环状的结构。*/ 93 if(!last[row]) 94 last[row]=size, 95 R[size]=size,L[size]=size, 96 f[row]=size; 97 else 98 L[size]=last[row],R[last[row]]=size, 99 last[row]=size, 100 R[size]=f[row],L[f[row]]=size; 101 102 /*列操作: 103 【与行操作有所不同】:我们事先已经构造出了矩阵的第一行,不存在所谓首元素,因为首元素就是这一列的标号。 104 如果这一列之前没有添加过元素,将当前元素与这一列的标号首尾相连,更新表示上一个元素的d[col]。 105 若之前添加过元素,将这个元素与上一个元素连起来;并将这个元素充当此行的末尾,与这一列的标号相连构成一个环状结构;更新d[col]。*/ 106 if(!d[col]) 107 U[col]=size,D[size]=col, 108 D[col]=size,U[size]=col, 109 d[col]=size; 110 111 else 112 U[col]=size,D[size]=col, 113 U[size]=d[col],D[d[col]]=size, 114 d[col]=size; 115 116 c[size]=col,r[size]=row; //处理完与其它节点的关系后,再给自己打上行列的标号 117 } 118 119 int main(){ 120 int k,x; 121 122 while(~scanf("%d%d",&n,&m)){ 123 build(); 124 size=m; //前m个元素已经使用过了,不参与计数 125 for(int i=1;i<=n;i++){ 126 scanf("%d",&k); 127 while(k--) 128 scanf("%d",&x),link(i,x); 129 } 130 if(!search(0)) puts("NO"); 131 } 132 133 return 0; 134 }