#include<bits/stdc++.h>
using namespace std;
const int maxnode = 100010;
const int maxm = 1010;
const int maxn = 1010;
struct DLX{
int n,m,sizes;//行数,列数,‘1’的总数
int D[maxnode],U[maxnode],L[maxnode],R[maxnode];//U D R L用来记录某个标号的节点的上下左右节点的编号
int Row[maxnode],Col[maxnode];//Row Col用来记录某个标号的节点在矩阵中的行号和列号
int H[maxn],S[maxm];//H是行头,S保存某一列中1的数量
int ansd,ans[maxn];//ansd是答案行数,ans[maxn]是每行行号
void init(int _n,int _m){//初始化列头
n=_n,m=_m;//读出行列数
for(int i=0; i<=m; i++){
S[i]=0;//每列‘1’的数量为0
D[i]=U[i]=i;//i号结点的上下节点编号是i即本身结点编号,因为只有其一个
L[i]=i-1,R[i]=i+1;//i号结点的左右节点编号是i-1,i+1
}
R[m]=0,L[0]=m;//0号点与m号点循环
sizes=m;//至此前m(还有0号点)个点初始化完成
for(int i=1; i<=n; i++) H[i]=-1;
}
void Link(int r,int c){//连边!第R行,第C列写1
++S[Col[++sizes]=c];//总'1'量SIZES加1个,然后第SIZES编号的节点的列是c,该列‘1’数量即S数组+1
Row[sizes]=r;//此节点行号是r
U[sizes]=U[c];//本结点的上指针指向表头指的上指针,实即上次本列的最后一个1编号
D[U[c]]=sizes;//上次本列的最后一个编号的下指针指向本结点
D[sizes]=c;//本结点的下指针指向表头
U[c]=sizes;//本列表头上指针指向本结点
if(H[r]<0)//如果此行未有点,即head指针仍为-1,则此点的head指针及左右指针均指向这个点
H[r]=L[sizes]=R[sizes]=sizes;
else{//如果此行已有点
R[sizes]=R[H[r]];//此点的右指针指向本行头指针的右结点
L[R[H[r]]]=sizes;//上次本行的最右点的左指针指向本结点(不是该右吗)
L[sizes]=H[r];//本结点左指针为本行头指针,注意h[r]从未变过,就是本行第一个‘1’的位置
R[H[r]]=sizes;//行首右指针改为本结点
}
}
void remove(int c){//删除某列!并删除列中为1的行
L[R[c]]=L[c];R[L[c]]=R[c];//右点左针指左点,左点右针指右点,注意控制的只是表头!!!
for(int i=U[c]; i!=c; i=U[i])//往下逐行扫,循环回到本行
for(int j=R[i]; j!=i; j=R[j]){//往右逐列扫,循环回到本列
D[U[j]]=D[j];//当前j结点上面的结点的下面的结点记为j结点上面的结点,实现删除本结点
U[D[j]]=U[j];//当前j结点下面的结点的上面的结点记为j结点下面的结点,实现删除本结点
--S[Col[j]];//本结点所在行的‘1’数量减一
}
}
void resume(int c){//反着恢复某列!并恢复列中为1的行
for(int i=D[c]; i!=c; i=D[i])//往下走
for(int j=L[i]; j!=i; j=L[j]){//往左走
D[U[j]]=U[D[j]]=j;//上面的下针,下面的上针指向本结点
++S[Col[j]];//计数加1
}
L[R[c]]=R[L[c]]=c;//右面的左针,左面的右针指本列,注意这是表头!!!
}
bool dance(int d){//舞蹈一下,可得答案!d为递归深度
if(R[0]==0){ansd=d;return true;}//行表头循环搜回了自身,结束
int c=R[0];
for(int i=R[0]; i!=0; i=R[i])if(S[i]<S[c])c=i;
remove(c);//找到列中包含1最多的列因为这样有助于减少递归深度(1多了删掉的行也多矩阵缩小得快)
for(int i=U[c]; i!=c; i=U[i]){//扫一次被删列中‘1’的编号,注意remove未对c列有任何操作(只改两侧)
ans[d]=Row[i];//本次搜索的深度就是这次‘1’所在的行了
for(int j=R[i]; j!=i; j=R[j])remove(Col[j]);//然后删除该列及列中为1的行
if(dance(d+1))return true;//继续搜索
for(int j=L[i]; j!=i; j=L[j])resume(Col[j]);//如不能成功则依次恢复
}
resume(c);//每次搜索完如果不能成功就要回复这一列
return false;
}
}dlx;
int main(){
int n,m,num,j;
while(scanf("%d%d",&n,&m)!=EOF){
dlx.init(n,m);
for(int i=1; i<=n; i++){
scanf("%d",&num);
while(num--){
scanf("%d",&j);
dlx.Link(i,j);
}
}
if(!dlx.dance(0)) printf("NO\n");
else{
printf("%d",dlx.ansd);
for(int i=0; i<dlx.ansd; i++)
printf(" %d",dlx.ans[i]);
printf("\n");
}
}
return 0;
}
HUST 1017 Exact cover(舞蹈链 入门题)
题意:每一行的某些列给定为1,现在问是否能找某些行,使得每一列的1出现一次
样例输入
6 7
3 1 4 7
2 1 4
3 4 5 7
3 3 5 6
4 2 3 6 7
2 2 7
样例输出
3 2 4 6