1067. 精确覆盖问题
题目链接
1067. 精确覆盖问题
给定一个 \(N \times M\) 的数字矩阵 \(A\),矩阵中的元素 \(A_{i,j} \in \lbrace 0,1 \rbrace\)。
请问,你能否在矩阵中找到一个行的集合,使得这些行中,每一列都有且仅有一个数字 \(1\)。
输入格式
第一行包含两个整数 \(N\) 和 \(M\)。
接下来 \(N\) 行,每行包含 \(M\) 个整数(\(0\) 或 \(1\)),表示完整的数字矩阵。
输出格式
如果能找到满足条件的行的集合,则在一行中依次输出这些行的编号(行编号 \(1 \sim N\))。
如果方案不唯一,则以任意顺序输出任意方案即可。
否则,输出 No Solution!
。
数据范围
\(1 \le N,M \le 500\),
数据保证矩阵中 \(1\) 的数量不超过 \(5000\)。
输入样例1:
3 3
0 1 0
0 0 1
1 0 0
输出样例1:
1 2 3
输入样例2:
4 4
0 0 0 1
1 0 0 0
1 1 0 1
0 1 0 0
输出样例2:
No Solution!
解题思路
DLX,精确覆盖
DLX 是一种基于十字链表的搜索算法,主要用来解决精确覆盖问题和重复覆盖问题
对于十字链表,即针对稀疏矩阵,即矩阵中 \(1\) 的个数不多,对于矩阵中 \(1\) 的位置都建立一个节点,每个节点都有上、下、左、右四个循环指针,建立十字链表时一行一行插入,首先插入一行哨兵,后面插入时统一逆插,即将所有矩阵上下和左右翻转,唯一的区别是哨兵行的左右指针没有翻转,这样便区分开了,但要找的是行,对答案没有影响
优化:找到一个含 \(1\) 最少的列,然后再该列中枚举选择哪一行,如果选择某一行,则其他行应该删除,因为精确覆盖每一列有且只有一个 \(1\),另外对于选择的行,其中出现 \(1\) 的列也应该删除,另外需要注意的一点:删除列时对于真正节点只需要在上下方向删除,因为后面对于某含最少个数的 \(1\) 的列,通过上下指针枚举行,而那些要删除的行我们已经在上下方向删除了不会被枚举到,恢复时反向也好恢复
DLX 处理精确覆盖问题甚至有时能处理 \(O(10000)\) 的数据
设 \(n\) 为矩阵中 \(1\) 的个数,\(c\) 为接近 \(1\) 的常数,则:
- 时间复杂度:\(O(c^n)\)
代码
// Problem: 精确覆盖问题
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/description/1069/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
// %%%Skyqwq
#include <bits/stdc++.h>
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template <typename T> void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
const int N=5505;
int n,m;
int L[N],R[N],U[N],D[N],s[N],col[N],row[N],idx;
int res[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]++;
L[tt]=R[hh]=idx,L[idx]=hh,R[idx]=tt;
U[idx]=y,D[idx]=D[y],U[D[y]]=idx,D[y]=idx;
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])
{
res[++top]=row[i];
for(int j=L[i];j!=i;j=L[j])remove(col[j]);
if(dfs())return true;
for(int j=R[i];j!=i;j=R[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 hh=idx,tt=idx;
for(int j=1;j<=m;j++)
{
int x;
scanf("%d",&x);
if(x)add(hh,tt,i,j);
}
}
if(dfs())
for(int i=1;i<=top;i++)printf("%d ",res[i]);
else
puts("No Solution!");
return 0;
}