hust 1017Exact cover
就是knuth论文里拿来举例子的题, 算是精确覆盖的裸题了。
#include <cstdio>
#include <cstring>
/** 在01矩阵找一个行集合,使其精确覆盖所有的列
所谓精确覆盖就是所有行中含1的列有且仅有一个,
而重复覆盖允许有多个。
**/
const int maxn=100000+123;
const int maxc=1000+5;
int S[maxc], L[maxn], R[maxn], D[maxn], U[maxn];
int H[maxc], ok[maxc], sz, C[maxn], mark[maxn];
///H是横向的表头, 不知道必不必要
void Link(int row, int col)
{
S[col]++; C[sz]=col;///C域指向列头
U[sz]=U[col]; D[U[col]]=sz;
D[sz]=col; U[col]=sz;
if(H[row]==-1)H[row]=L[sz]=R[sz]=sz;
else
{
L[sz]=L[H[row]]; R[L[H[row]]]=sz;
R[sz]=H[row]; L[H[row]]=sz;
}
mark[sz]=row;/// 标记每个点是哪一行(题目要求输出解属于哪一行)
sz++;
}
void remove(int col)
{
L[R[col]]=L[col];
R[L[col]]=R[col]; /// 在列对象链表中删除col
for (int i=D[col]; i!=col; i=D[i])
{/// 删除col列中有1元素的行
for (int j=R[i]; j!=i; j=R[j])
{///删除每行的1元素,并修改所在列的S域
U[D[j]]=U[j], D[U[j]]=D[j];
S[C[j]]--;
}
}
}
void resume(int col)
{
for (int i=U[col]; i!=col; i=U[i])
{
for (int j=L[i]; j!=i; j=L[j])
{
U[D[j]]=j; D[U[j]]=j;
S[C[j]]++;
}///恢复删除的元素,恢复S域
}///恢复删除的行
L[R[col]]=col;
R[L[col]]=col;
}
bool Dance(int k)
{
//printf("%d\n", k);
if(R[0]==0)
{
printf("%d", k);
for (int i=0; i<k; ++i)
printf(" %d", ok[i]);
puts("");
return true;
}
int c=R[0];
for(int i=R[0]; i; i=R[i])
if(S[i]<S[c])c=i;
remove(c);
// printf("remove col %d\n", c);
for(int i=D[c]; i!=c; i=D[i])
{
ok[k]=mark[i];
for (int j=R[i]; j!=i; j=R[j])
remove(C[j]);
if(Dance(k+1))return true;
for (int j=L[i]; j!=i; j=L[j])
resume(C[j]);
}
resume(c);
return false;
}
void initL(int x)
{
for (int i=0; i<=x; ++i)///i<x???
{
S[i]=0;
D[i]=U[i]=i;
L[i+1]=i; R[i]=i+1;
}///对列表头初始化
R[x]=0;
sz=x+1;///真正的元素从m+1开始
memset (H, -1, sizeof(H));
///mark每个位置的名字
}
int main()
{
int n, m;
while(~scanf("%d%d", &n, &m))
{
initL(m);
for (int i=1; i<=n; ++i)
{
int k; scanf("%d", &k);
while (k--)
{
int x; scanf("%d", &x);
Link(i, x);
}
}
if(!Dance(0))puts("NO");
}
return 0;
}