1. 程式人生 > 實用技巧 >【洛谷P4929】【模板】舞蹈鏈(DLX)

【洛谷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;
}