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

预备知识:
跳跃的舞者,舞蹈链(Dancing Links)算法——求解精确覆盖问题
夜深人静写算法(九)- Dancing Links X(跳舞链)

题目链接:
https://www.luogu.org/problem/P4929

题目背景

本题是舞蹈链模板——精确覆盖问题

题目描述

给定一个N行M列的矩阵,矩阵中每个元素要么是1,要么是0

你需要在矩阵中挑选出若干行,使得对于矩阵的每一列j,在你挑选的这些行中,有且仅有一行的第j个元素为1

输入格式

第一行两个数N,M

接下来N行,每行M个数字0或1,表示这个矩阵

输出格式

一行输出若干个数表示答案,两个数之间用空格隔开,输出任一可行方案均可,顺序随意

若无解,输出“No Solution!”(不包含引号)

输入输出样例

输入 #1

3 3
0 0 1
1 0 0
0 1 0

输出 #1

2 1 3

输入 #2

3 3
1 0 1
1 1 0
0 1 1

输出 #2

No Solution!

说明/提示

N,M≤500

保证矩阵中1的数量不超过5000个

 

建图方式:

 

结点类型以及所需变量:

typedef struct DLXnode
{
	int x,y;
	DLXnode *up,*down,*left,*right;
}D;
D node[MAXN*MAXN];
D row[MAXN];
D col[MAXN];
D *head=new D;
int cnt,n,m,num[MAXN],ans[MAXN];

x和y表示每个结点的横、纵坐标,其中,总表头head的坐标为(0,0),列首结点的横坐标为0,行首结点的纵坐标为1,元素结点的横、纵坐标对应于给定矩阵中1的横、纵坐标;
up、down、left和right表示指向四个方向的指针;
node数组存储的是元素结点;
row数组和col数组存储的是行首结点和列首结点;
head指针指向的是总表头;
cnt表示元素结点的个数,n代表给定矩阵的行,m代表给定矩阵的列;
num数组存储的是每一列'1'的个数
,即num[i]表示第i列'1'的个数;
ans数组存储的是解。

 

初始化图init:

void init(int n,int m)
{
	cnt=0,head->x=0,head->y=0;
	head->down=head->left=head->right=head->up=head;
	for(int i=1;i<=m;++i)
	{
		col[i].x=0,col[i].y=i;
		col[i].right=head,col[i].left=head->left;
		col[i].right->left=col[i].left->right=&col[i];
		col[i].up=col[i].down=&col[i];
		num[i]=0;//第i列1的个数,初始化为0 
	}
	for(int i=1;i<=n;++i)
	{
		row[i].x=i,row[i].y=0;
		row[i].down=head,row[i].up=head->up;
		row[i].up->down=row[i].down->up=&row[i];
		row[i].left=row[i].right=&row[i];
	}
}

如图:

添加元素结点add:

inline void add(int x,int y)
{
	D *p=&node[++cnt];
	p->x=x,p->y=y;
	p->right=&row[x],p->left=row[x].left;
	p->left->right=p->right->left=p;
	p->down=&col[y],p->up=col[y].up;
	p->up->down=p->down->up=p;
	++num[y];
}

主函数里添加元素结点的顺序是:

n=read(),m=read();
init(n,m);
for(int i=1;i<=n;++i)
{
	for(int j=1;j<=m;++j)
	if(read()) add(i,j);
}

 

移除结点del以及恢复结点re:

inline void delLR(D *p)//将p指向的结点在水平方向移除 
{
	p->right->left=p->left;
	p->left->right=p->right;
}
inline void delUD(D *p)//将p指向的结点在竖直方向移除
{
	p->up->down=p->down;
	p->down->up=p->up;
}
inline void reLR(D *p)//将p指向的结点在水平方向恢复
{
	p->left->right=p->right->left=p;
}
inline void reUD(D *p)//将p指向的结点在竖直方向恢复
{
	p->up->down=p->down->up=p;
}

如图:

 

移除列cover和相关的行以及恢复:

inline void cover(int c)//移除第c列 并且 将该列有'1'的行也移除 
{
	delLR(&col[c]);//将第c列的列首结点在水平方向断开 
	D *i,*j;
	for(i=col[c].down;i!=&col[c];i=i->down) //遍历该列找有'1'的行 
	{
		for(j=i->right;j!=i;j=j->right)//移除该行
		{
			--num[j->y];//第j->y列的'1'减少 
			delUD(j);//将该行的元素结点在竖直方向断开 
		}
		delLR(i);//将第c列的元素结点在水平方向断开 
	}
}
inline void re(int c)//恢复第c列 以及 恢复该列有'1'的行 
{
	D *i,*j;
	for(i=col[c].down;i!=&col[c];i=i->down)
	{
		reLR(i);
		for(j=i->right;j!=i;j=j->right)
		{
			reUD(j);
			++num[j->y];
		}	
	}
	reLR(&col[c]);
}

如图:

移除第c列时首先将列首结点在水平方向断开,然后向下(或向上)遍历将其元素结点也在水平方向断开。
这里由于还要将第c列有'1'即元素结点所在的行也进行移除,故在第c列每行的元素结点在水平方向断开之前,将该行所有结点在竖直方向断开,更新每列还剩下'1'的个数。
恢复第c列相关的操作可看做是移除操作的逆过程,逆着来一遍就行。

 

开始跳舞Dance:

bool dance(int k)
{
	if(head->right==head)//空矩阵 
	{
		for(int i=1;i<k;++i)//输出解 
		cout<<ans[i]<<' ';
		return true;
	}
	int c=-1,tmp=INF;
	D *i,*j;
	for(i=head->right;i!=head;i=i->right)
	{
		if(num[i->y]<tmp)//选每一列1最少的列c,确定性选择 
		{
			tmp=num[i->y];
			c=i->y;
		}
	}
	cover(c);//移除第c列和该列有1的行 
	
	for(i=col[c].down;i!=&col[c];i=i->down)//在列c中选行
	{
		i->left->right=i;//(1) 
		for(j=i->right;j!=i;j=j->right)
		if(j->y) cover(j->y);//移除该行有1的列以及该列有1的行
		i->left->right=i->right;//这句和(1)句是为了避免死循环 
		ans[k]=i->x;//将该行加入可行解列表 
		if(dance(k+1)) return true;//移除一些行和列后规模缩小,递归求解
		//回溯 
		i->right->left=i;
		for(j=i->left;j!=i;j=j->left)
		if(j->y) re(j->y);
		i->right->left=i->left; 
	}
	re(c);
	return false;//如果列c中无满足的行,那肯定GG了 
}

我们重新思考一遍 X 算法

  1. 如果矩阵A没有列(即空矩阵),则当前记录的解为一个可行解;算法终止,成功返回;
  2. 否则选择矩阵A中“1”的个数最少的列c;(确定性选择)
  3. a:如果存在A[r][c]=1的行r,将行r放入可行解列表,进入步骤4(非确定性选择)
    b:如果不存在A[r][c]=1的行r,则剩下的矩阵不可能完成精确覆盖,说明之前的选择有错(或者根本就无解),需要回溯,并且恢复此次删除的行和列,然后跳到步骤3.a
  4. 对于所有的满足A[r][j]=1的列j,对于所有满足A[i][j]=1的行i,将行i从矩阵A中删除;将列j从矩阵A中删除;
  5.  在不断减少的矩阵A上递归重复调用上述算法;

上述引用来自https://www.luogu.org/blog/Setsugesuka/solution-p4929

 

AC代码:

#include<iostream>
#include<sstream>
#include<fstream>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<cstdlib>
#include<cctype>
#include<vector>
#include<string>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<map>
#include<set>
#define mem(a,b) memset(a,b,sizeof(a))
#define random(a,b) (rand()%(b-a+1)+a)
#define ll long long
#define ull unsigned long long
#define e 2.71828182
#define Pi acos(-1.0)
#define ls(rt) (rt<<1)
#define rs(rt) (rt<<1|1)
#define lowbit(x) (x&(-x))
using namespace std;
const int MAXN=5e2+5; 
const int INF=0x3f3f3f3f;
int read()
{
	int s=1,x=0;
	char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-') s=-1;ch=getchar();}
	while(isdigit(ch)) {x=10*x+ch-'0';ch=getchar();}
	return x*s;
}
typedef struct DLXnode
{
	int x,y;
	DLXnode *up,*down,*left,*right;
}D;
D node[MAXN*MAXN];
D row[MAXN];
D col[MAXN];
D *head=new D;
int cnt,n,m,num[MAXN],ans[MAXN];

void init(int n,int m)
{
	cnt=0,head->x=0,head->y=0;
	head->down=head->left=head->right=head->up=head;
	for(int i=1;i<=m;++i)
	{
		col[i].x=0,col[i].y=i;
		col[i].right=head,col[i].left=head->left;
		col[i].right->left=col[i].left->right=&col[i];
		col[i].up=col[i].down=&col[i];
		num[i]=0;//第i列1的个数,初始化为0 
	}
	for(int i=1;i<=n;++i)
	{
		row[i].x=i,row[i].y=0;
		row[i].down=head,row[i].up=head->up;
		row[i].up->down=row[i].down->up=&row[i];
		row[i].left=row[i].right=&row[i];
	}
}
inline void add(int x,int y)
{
	D *p=&node[++cnt];
	p->x=x,p->y=y;
	p->right=&row[x],p->left=row[x].left;
	p->left->right=p->right->left=p;
	p->down=&col[y],p->up=col[y].up;
	p->up->down=p->down->up=p;
	++num[y];
}
inline void delLR(D *p)
{
	p->right->left=p->left;
	p->left->right=p->right;
}
inline void delUD(D *p)
{
	p->up->down=p->down;
	p->down->up=p->up;
}
inline void reLR(D *p)
{
	p->left->right=p->right->left=p;
}
inline void reUD(D *p)
{
	p->up->down=p->down->up=p;
}
inline void cover(int c)
{
	delLR(&col[c]);
	for(D *i=col[c].down;i!=&col[c];i=i->down)
	{
		for(D *j=i->right;j!=i;j=j->right)
		--num[j->y],delUD(j);
		delLR(i);
	}
}
inline void re(int c)
{
	for(D *i=col[c].down;i!=&col[c];i=i->down)
	{
		reLR(i);
		for(D *j=i->right;j!=i;j=j->right)
		reUD(j),++num[j->y];		
	}
	reLR(&col[c]);
}
bool dance(int k)
{
	if(head->right==head)
	{
		for(int i=1;i<k;++i)
		cout<<ans[i]<<' ';
		return true;
	}
	int c=-1,tmp=INF;
	for(D *i=head->right;i!=head;i=i->right)
	if(num[i->y]<tmp) tmp=num[i->y],c=i->y;
	cover(c);
	for(D *i=col[c].down;i!=&col[c];i=i->down)
	{
		i->left->right=i;
		for(D *j=i->right;j!=i;j=j->right)
		if(j->y) cover(j->y);
		i->left->right=i->right;
		ans[k]=i->x;
		if(dance(k+1)) return true;
		i->left->right=i;
		for(D *j=i->right;j!=i;j=j->right)
		if(j->y) re(j->y);
		i->left->right=i->right;
	}
	re(c);
	return false;
}
int main()
{
	n=read(),m=read();
	init(n,m);
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=m;++j)
		if(read()) add(i,j);
	}
	if(!dance(1))
	cout<<"No Solution!\n";
}
/*
6 7
0 0 1 0 1 1 0
1 0 0 1 0 0 1
0 1 1 0 0 1 0
1 0 0 1 0 0 0
0 1 0 0 0 0 1
0 0 0 1 1 0 1
*/
/*
4 5 1
*/

后言:
后来用上述方式做POJ3074的时候,有、、慢(超时了)
所以以后还是写下面这种吧:
 

struct DLX
{
	int n,m,cnt;
	int U[MAXN],D[MAXN],R[MAXN],L[MAXN],row[MAXN],col[MAXN];
	int H[MAXN],S[MAXN];
	int ans[MAXN];
	void init(int _n,int _m)
	{
		n=_n,m=_m;
		for(int i=0;i<=m;++i)
		S[i]=0,U[i]=D[i]=i,L[i]=i-1,R[i]=i+1;
		R[m]=0,L[0]=m,cnt=m;
		for(int i=1;i<=n;++i)
		H[i]=-1;
	}
	void add(int r,int c)
	{
		++S[col[++cnt]=c];
		row[cnt]=r;
		D[cnt]=D[c],U[D[c]]=cnt;
		U[cnt]=c,D[c]=cnt;
		if(H[r]<0) H[r]=L[cnt]=R[cnt]=cnt;
		else R[cnt]=R[H[r]],L[R[H[r]]]=cnt,L[cnt]=H[r],R[H[r]]=cnt;
	}
	void remove(int c)
	{
		L[R[c]]=L[c],R[L[c]]=R[c];
		for(int i=D[c];i!=c;i=D[i])
		for(int j=R[i];j!=i;j=R[j])
		U[D[j]]=U[j],D[U[j]]=D[j],--S[col[j]];
	}
	void resume(int c)
	{
		for(int i=U[c];i!=c;i=U[i])
		for(int j=L[i];j!=i;j=L[j])
		++S[col[U[D[j]]=D[U[j]]=j]];
		L[R[c]]=R[L[c]]=c;
	}
	bool dance(int k)
	{
		if(R[0]==0)
		{
			/*for(int i=1;i<k;++i)
			cout<<ans[i]<<' ';*/
			return true;
		}
		int c=R[0];
		for(int i=R[0];i!=0;i=R[i])
		if(S[i]<S[c]) c=i;
		remove(c);
		for(int i=D[c];i!=c;i=D[i])
		{
			ans[k]=row[i];
			for(int j=R[i];j!=i;j=R[j]) remove(col[j]);
			if(dance(k+1)) return true;
			for(int j=L[i];j!=i;j=L[j]) resume(col[j]);
		}
		resume(c);
		return false;
	}
}dlx;

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值