【洛谷P4929】【模板】舞蹈链(DLX)
题目
题目链接:https://www.luogu.com.cn/problem/P4929
给定一个\(N\)行\(M\)列的矩阵,矩阵中每个元素要么是1,要么是0
你需要在矩阵中挑选出若干行,使得对于矩阵的每一列\(j\),在你挑选的这些行中,有且仅有一行的第\(j\)个元素为\(1\)。
\(N,M\leq 500\),数字 \(1\) 的数量 \(\leq 5000\)。
思路
DLX 可以解决的是精确覆盖问题。也就是任意一个位置被恰好覆盖一次。
进行操作后为了方便删除未被选择的无用操作,DLX 采用交叉十字循环双向链,使得单次删除和重新插入的复杂度均为 \(O(1)\)。
详解可以见 Blog。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=300010;
int n,m;
stack<int> st;
struct DLX
{
int tot,l[N],r[N],u[N],d[N],x[N],y[N],h[N],cnt[N];
void build(int m)
{
memset(h,-1,sizeof(h));
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; tot=m;
}
void ins(int i,int j)
{
int p=++tot;
x[p]=i; y[p]=j; cnt[j]++;
u[p]=u[j]; d[u[j]]=p; d[p]=j; u[j]=p;
if (h[i]==-1)
h[i]=p,l[p]=r[p]=p;
else
l[p]=l[h[i]],r[l[p]]=p,r[p]=h[i],l[h[i]]=p;
}
void remove(int k)
{
r[l[k]]=r[k]; l[r[k]]=l[k];
for (int i=d[k];i!=k;i=d[i])
for (int j=l[i];j!=i;j=l[j])
d[u[j]]=d[j],u[d[j]]=u[j],cnt[y[j]]--;
}
void resume(int k)
{
for (int i=u[k];i!=k;i=u[i])
for (int j=r[i];j!=i;j=r[j])
d[u[j]]=j,u[d[j]]=j,cnt[y[j]]++;
r[l[k]]=k; l[r[k]]=k;
}
bool dance()
{
if (!r[0])
{
for (;st.size();st.pop())
printf("%d ",st.top());
return 1;
}
int p=0;
for (int i=r[0];i;i=r[i])
if (cnt[i]<cnt[p] || !p) p=i;
if (!p) return 0;
remove(p);
for (int i=d[p];i!=p;i=d[i])
{
st.push(x[i]);
for (int j=l[i];j!=i;j=l[j]) remove(y[j]);
if (dance()) return 1;
for (int j=r[i];j!=i;j=r[j]) resume(y[j]);
st.pop();
}
resume(p);
return 0;
}
}dlx;
int main()
{
scanf("%d%d",&n,&m);
dlx.build(m);
for (int i=1;i<=n;i++)
for (int j=1,x;j<=m;j++)
{
scanf("%d",&x);
if (x) dlx.ins(i,j);
}
if (!dlx.dance()) printf("No Solution!");
return 0;
}