【洛谷P4929】【模板】舞蹈链(DLX)

题目

题目链接:https://www.luogu.com.cn/problem/P4929
给定一个(N)(M)列的矩阵,矩阵中每个元素要么是1,要么是0
你需要在矩阵中挑选出若干行,使得对于矩阵的每一列(j),在你挑选的这些行中,有且仅有一行的第(j)个元素为(1)
(N,Mleq 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;
}
原文地址:https://www.cnblogs.com/stoorz/p/14246850.html