题目链接:[kuangbin带你飞]专题三 Dancing Links A - Exact cover
题意
给定一01矩阵,问是否能够精确覆盖(就是选取任意行,这些行的1所在的列互不冲突且完整覆盖所有列),若有输出行号(要按递增顺序输出),否则输出NO。
思路
ps:两个礼拜前大略看了下舞蹈链(虽然英文名听起来更高端,但还是更喜欢它的中文名字),很精妙但也让人一看就惰性必生不愿再看,今天耐心再仔细理解了下,总算是a的刷题生涯第一道精确覆盖题(人有时候还是要逼自己一把,战胜惰性才能进步)。
没什么思路,标准的舞蹈链模版题,理解了舞蹈链就能a了。
学习舞蹈链,推荐此博文,相当清晰:跳跃的舞者,舞蹈链(Dancing Links)算法——求解精确覆盖问题
代码上有详细的注释,但有一处有必要分享一下。
for(int i=R[0]; i!=0; i=R[i])
if(S[i] < S[col])
col = i;
上面这段代码作用是每次选取元素最少的列进行操作,可以有效减少递归层数,从而加快程序效率。
因为本人好奇,取消了它试了一下,发现wrong(按理也应该是timelimit啊),一试再试,发现是得出的行号没有排序的原因,加上对结果的排序就好了。
但本人又好奇了,一再思索,终究还是想不出来上述代码为什么能够使结果递增序,于是做了一组数据:
5 5
2 4 5
3 1 3 4
1 5
1 2
2 1 5
测试后发现无论加不加上述代码结果都不是递增序的,也就是说上述代码只是加速的功能而已。
也就是说,测试数据太水,所有没加排序的代码都应该被wrong。(很好奇那么多题解为什么没有一个带排序的,希望自己不要成为为了ac而ac的人)。
代码
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <vector>
using namespace std;
const int N = 1009;
const int MAX = 1000009;
int U[MAX], D[MAX], L[MAX], R[MAX];//数组模拟链表指向(上下左右)
int C[MAX], M[MAX];//节点所在列与行
int S[N];//储存每列的元素数量
int H[N];//行头指针
int ANS[N];//结果保存数组
void link(int row, int col, int id)//将节点加入链表
{
C[id] = col; M[id] = row;//记录行列
U[id] = U[col]; D[U[col]] = id;//上下连接
D[id] = col; U[col] = id;
if(H[row] == -1)//左右连接(使用表头方便头插)
H[row] = L[id] = R[id] = id;
else
{
L[id] = L[H[row]]; R[L[H[row]]] = id;
L[H[row]] = id; R[id] = H[row];
}
S[col]++;
}
void remove(int col)//删除列
{
R[L[col]] = R[col];
L[R[col]] = L[col];
for(int i=D[col]; i!=col; i=D[i])
{
for(int j=R[i]; j!=i; j=R[j])
{
U[D[j]] = U[j];
D[U[j]] = D[j];
S[C[j]]--;
}
}
}
void resume(int col)//恢复列(先删的后恢复,后删的先恢复,所以跟remove反向操作)
{
R[L[col]] = col;
L[R[col]] = 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]]++;
}
}
}
bool dance(int k)
{
if(!R[0])//列辅助数组为空表示已得解
{
printf("%d", k);
sort(ANS, ANS+k);//对结果排序。
for(int i=0; i<k; i++)
printf(" %d", ANS[i]);
printf("\n");
return true;
}
int col = R[0];
for(int i=R[0]; i!=0; i=R[i])//加速,上文已说明
if(S[i] < S[col])
col = i;
remove(col);//删除列
for(int i=D[col]; i!=col; i=D[i])//尝试该列每行一次做为解
{
ANS[k] = M[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(col);//恢复列
return false;
}
int main()
{
int n, m;
while(~scanf("%d%d", &n, &m))
{
for(int i=0; i<=m; i++)//初始化
{
L[i+1] = i;
R[i] = i+1;
U[i] = D[i] = i;
S[i] = 0;
}
L[0] = m;
R[m] = 0;
int id = m+1;
for(int i=1; i<=n; i++)
{
int num;
scanf("%d", &num);
H[i] = -1;
while(num--)
{
int col;
scanf("%d", &col);
link(i, col, id++);
}
}
if(!dance(0))
printf("NO\n");
}
return 0;
}