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;
}
posted @ 2022-11-19 11:24  zyy2001  阅读(29)  评论(0编辑  收藏  举报