Problem
There is an N*M matrix with only 0s and 1s, (1 <= N,M <= 1000). An exact cover is a selection of rows such that every column has a 1 in exactly one of the selected rows.
Try to find out the selected rows.
Input
There are multiply test cases.
First line: two integers N, M;
The following N lines: Every line first comes an integer C(1 <= C <= 100), represents the number of 1s in this row, then comes C integers: the index of the columns whose value is 1 in this row.
Output
First output the number of rows in the selection, then output the index of the selected rows.
If there are multiply selections, you should just output any of them.
If there are no selection, just output “NO”.
Sample Input
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
Sample Output
3 2 4 6
运用十字双向链表(即Dancing Links)这一存储结构存储01稀疏矩阵(元素1的数量较少)中值为1的节点,之后选择1个数最少的列(剪枝),删除(remove)元素最少的那一列,减少搜索次数
从上往下遍历这一列有元素(1)的行,每遍历到某一行则假定选择该行,删除(remove)这一行中有元素(1)的列,递归搜索该方案是否满足条件。由于有可能搜尽了某一分支都没有可行的解,那么则要回溯,注意恢复现场(resume)
Dancing Links模板题(AcWing 1067. 精确覆盖问题)代码加详细注释:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 5510;
int n, m;//矩阵的长宽
int l[N], r[N], u[N], d[N];//每个点上下左右的指针
int s[N];//每列的节点数
int row[N], col[N], idx;//每个点所在的行列及点的数量
int ans[N];//选了哪些集合 相当于一个存答案的栈
int top;//答案栈的栈顶指针
void init()//建立一个空矩阵
{
for (int i = 0; i <= m; i ++ )
{
l[i] = i - 1, r[i] = i + 1;
u[i] = d[i] = i;
}
l[0] = m, r[m] = 0;//0左边是m m右边是0
idx = m + 1;//一开始空矩阵有m+1个元素
}
void add(int& hh, int& tt, int x, int y)//在第x行y列插入一个元素(插入到hh,tt之间)
{
row[idx] = x, col[idx] = y, s[y] ++ ;//当前插入的点编号为idx 记录当前点的行列信息 第y列元素个数增加
u[idx] = y, d[idx] = d[y];
u[d[y]] = idx, d[y] = idx;//对于列:这个点是直接插到该列的虚拟节点的后面 构建双向十字链表-列
r[hh] = l[tt] = idx;//作为当前该行唯一的节点兼头节点 它的左右指针都指向它自己
r[idx] = tt, l[idx] = hh;
tt = idx ++ ;//tt后移
}
void remove(int p)//删除第p列及该列上有元素的每一行
{
r[l[p]] = r[p];//该列左边元素指向该列的右指针直接指向该列的右边 相当于直接把该列忽视掉了
l[r[p]] = l[p];//该列右边元素同理 直接指向该列左侧
for (int i = d[p]; i != p; i = d[i])//遍历该列上的元素
for (int j = r[i]; j != i; j = r[j])//遍历这个元素所在的这一行上的元素
{
s[col[j]] -- ;//这个点所在的列元素数量减1
u[d[j]] = u[j];//注意我们处理的点是该列有1的行内的点
d[u[j]] = d[j];//将我们要处理的点在这个点的纵向删除掉
}
}//不难发现 删除的本质分为两步:单独处理掉本列; 在横向以点为单位逐个删除掉
void resume(int p)//恢复本列及相关元素
{
for (int i = u[p]; i != p; i = u[i])//遍历相关行
for (int j = l[i]; j != i; j = l[j])//遍历行上的点
{
u[d[j]] = j;//将该点与上下节点的关系恢复回来
d[u[j]] = j;
s[col[j]] ++ ;//将该点所在的列上的元素个数加回来
}
//删除与恢复的对称
r[l[p]] = p;
l[r[p]] = p;
}
bool dfs()
{
if (!r[0]) return true;//矩阵空了 搜索成功
int p = r[0];
for (int i = r[0]; i; i = r[i])//从左到右遍历列
if (s[i] < s[p])//找到元素1个数最少的那一列
p = i;
remove(p);//删除元素最少的那一列 减少搜索次数
for (int i = d[p]; i != p; i = d[i])//从上往下遍历p列有元素的行
{
ans[ ++ top] = row[i];//假定选择该行
for (int j = r[i]; j != i; j = r[j]) remove(col[j]);//删除这一行中有元素的列
if (dfs()) return true;//递归搜索该方案是否满足条件
for (int j = l[i]; j != i; j = l[j]) resume(col[j]);//复原
top -- ;
}
resume(p);//不要忘记同时要恢复p本身这一列
return false;
}
int main()
{
scanf("%d%d", &n, &m);
init();
for (int i = 1; i <= n; i ++ )
{
int hh = idx, tt = idx;
for (int j = 1; j <= m; j ++ )
{
int x;
scanf("%d", &x);
if (x==1) add(hh, tt, i, j);//将需要的元素添加到链表中
}
}
if (dfs())
{
for (int i = 1; i <= top; i ++ ) printf("%d ", ans[i]);//输出选中了哪几行
puts("");
}
else puts("No Solution!");
return 0;
}
将上方的模板稍作扩展可得本题(Exact cover)代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N = 5510;
int n, m;
int l[N], r[N], u[N], d[N], s[N], row[N], col[N], idx;
int ans[N], top;
void init()
{
for (int i = 0; i <= m; i ++ )
{
l[i] = i - 1, r[i] = i + 1;
u[i] = d[i] = i;
}
l[0] = m, r[m] = 0;
idx = m + 1;
}
void add(int& hh, int& tt, int x, int y)
{
row[idx] = x, col[idx] = y, s[y] ++ ;
u[idx] = y, d[idx] = d[y], u[d[y]] = idx, d[y] = idx;
r[hh] = l[tt] = idx, r[idx] = tt, l[idx] = hh;
tt = idx ++ ;
}
void remove(int p)
{
r[l[p]] = r[p], l[r[p]] = l[p];
for (int i = d[p]; i != p; i = d[i])
for (int j = r[i]; j != i; j = r[j])
{
s[col[j]] -- ;
u[d[j]] = u[j], d[u[j]] = d[j];
}
}
void resume(int p)
{
for (int i = u[p]; i != p; i = u[i])
for (int j = l[i]; j != i; j = l[j])
{
u[d[j]] = j, d[u[j]] = j;
s[col[j]] ++ ;
}
r[l[p]] = p, l[r[p]] = p;
}
bool dfs()
{
if (!r[0]) return true;
int p = r[0];
for (int i = r[0]; i; i = r[i])
if (s[i] < s[p])
p = i;
remove(p);
for (int i = d[p]; i != p; i = d[i])
{
ans[ ++ top] = row[i];
for (int j = r[i]; j != i; j = r[j]) remove(col[j]);
if (dfs()) return true;
for (int j = l[i]; j != i; j = l[j]) resume(col[j]);
top -- ;
}
resume(p);
return false;
}
int main()
{
scanf("%d%d", &n, &m);
init();
for(int i=1;i<=n;++i)
{
int c;
cin>>c;
vector<int> a(c+1);
for(int j=1;j<=c;++j) cin>>a[j];
int p = 1;
int hh = idx,tt = idx;
for(int k=1;k<=m;++k)
{
if(k==a[p]) add(hh,tt,i,k),p++;
}
}
if (dfs())
{
cout<<top<<' ';
for (int i = 1; i < top; i ++ ) printf("%d ", ans[i]);
cout<<ans[top]<<endl;
cout<<endl;
}
else cout<<"NO"<<endl;
return 0;
}