浅谈舞蹈链(DLX)

名字:

D L DL DL D a n c i n g   L i n k Dancing\space Link Dancing Link,舞蹈链,是由 D o n a l d   K n u t h Donald\space Knuth Donald Knuth提出的数据结构,用来优化 X X X 算法,所以叫 D L X DLX DLX

X X X算法详解

用于求解精确覆盖问题,精确覆盖问题的定义:给定一个由0-1组成的矩阵,是否能找到一个行的集合,使得集合中每一列都恰好包含一个1

例:

A , B , C ⊆ S A,B,C \subseteq S A,B,CS

A   ∩   B = ∅ A \space \cap \space B=\empty A  B= A   ∪   B = S A \space \cup \space B=S A  B=S则集合 A , B A,B A,B是问题的一种解

X X X算法求解模拟:

给出矩阵 A A A
A = ( 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 ) A=\left( \begin{matrix} 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 \end{matrix} \right) A= 010100001010101000010101100001101000010011

选择第一列:
( 0 0 1 0 1 1 0 ) \left( \begin{matrix} 0&0&1&0&1&1&0\\\\\\\\\\ \end{matrix} \right) 0010110
将有 1 1 1的列向下延伸,若该行有 1 1 1,标记该行,处理 A A A矩阵,得到 B B B矩阵
B = ( 0 0 1 0 1 1 0 0 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0 1 1 0 1 ) B=\left( \begin{matrix} 0&0&1&0&1&1&0\\&&0&&0&0\\0&1&1&0&0&1&0\\&&0&&0&0\\&&0&&0&0\\0&0&0&1&1&0&1 \end{matrix} \right) B= 000010101000001100001101000001
S 矩阵 = A 矩阵 − B 矩阵 S矩阵=A矩阵-B矩阵 S矩阵=A矩阵B矩阵(删除所标记的行和列)
A 1 = ( 1 0 1 1 1 0 1 0 0 1 0 1 ) A_1=\left( \begin{matrix} 1&0&1&1\\1&0&1&0\\0&1&0&1 \end{matrix} \right) A1= 110001110101
再选择第一列
( 1 0 1 1 ) \left( \begin{matrix} 1&0&1&1\\\\\\ \end{matrix} \right) 1011
标记
B 1 = ( 1 0 1 1 1 1 0 0 1 0 1 ) B_1= \left( \begin{matrix} 1&0&1&1\\1&&1&0\\0&1&0&1 \end{matrix} \right) B1= 11001110101
得到新矩阵,又得到一个规模较小的精确覆盖问题
S 1 = ( 0 ) S_1= \left( \begin{matrix} 0 \end{matrix} \right) S1=(0)
发现 A A A矩阵不是空的,也没有一列有 1 1 1(无法继续操作)

回溯

A 1 = ( 1 0 1 1 1 0 1 0 0 1 0 1 ) A_1=\left( \begin{matrix} 1&0&1&1\\1&0&1&0\\0&1&0&1 \end{matrix} \right) A1= 110001110101

不能尝试第一行,标记第二行,尝试继续拓展
B 2 = ( 1 0 1 1 1 0 1 0 0 0 ) B_2= \left( \begin{matrix} 1&0&1&1\\1&0&1&0\\0&&0 \end{matrix} \right) B2= 1100011010
同理可得
A 3 = ( 1 1 ) A_3= \left( \begin{matrix} 1&1 \end{matrix} \right) A3=(11)
A 3 A_3 A3中只有 1 1 1行,且都是 1 1 1,选择这行,问题就可以解决

故,问题的解就是矩阵 A A A中的第一行、矩阵 A 1 A_1 A1中的第 2 2 2行、矩阵 A 3 A_3 A3的第一行,即矩阵 A A A中的第 1 、 4 、 5 1、4、5 145

言归正传,DLX又是什么东西呢

介绍DL,舞蹈链


例子引用
建出形如这样的交叉十字循环双向链只对有 1 1 1的连边,这张图对应着矩阵 A A A

获取 h e a d . r i g h t head.right head.right元素,即元素 C 1 C1 C1,标示元素 C 1 C1 C1为紫色

先尝试行 2 2 2,标示该行中其他元素 元素 5 和元素 6 元素5和元素6 元素5和元素6所在的列首元素为橙色,即元素 C 4 C4 C4和元素 C 7 C7 C7

移除橙色部分和紫色部分,剩下的如图所示

获取 h e a d . r i g h t head.right head.right元素,即元素 C 2 C2 C2,标示元素 C 2 C2 C2为紫色

C 2 C2 C2只有元素 7 7 7覆盖,故答案只能选择行 3 3 3

选择行 3 3 3,同理,标示元素 C 3 C3 C3和标示元素 C 6 C6 C6为橙色

移除,得

没有元素能覆盖到 C 5 C5 C5,说明求解失败,回溯

回标列首元素,其顺序是标示元素的顺序反过来,即回标列首C6、回标列首C3、回标列首C2、回标列首C7、回标列首C4

故不能选择行 2 2 2,尝试行 4 4 4,同理,标示元素 C 4 C4 C4为橙色

移除,得

获取 h e a d . r i g h t head.right head.right元素,标示元素 C 2 C2 C2为紫色

选择行 3 3 3,同理,标示元素 C 3 C3 C3 C 6 C6 C6为橙色

同理,得

同理,回溯,回标列首C6、回标列首C3

这次选择行 5 5 5,标示元素 C 7 C7 C7为橙色

移除,得

获取 h e a d . r i g h t head.right head.right元素,标示元素 C 3 C3 C3为紫色

只有元素 1 1 1覆盖,故答案只能选择行 3 3 3,同理,标示元素 C 5 C5 C5和元素 C 6 C6 C6为橙色

移除,得

因为 h e a d . r i g h t = h e a d head.right=head head.right=head,故求解结束,答案栈中的答案分别是 4 、 5 、 1 4、5、1 451,表示该问题的解是第 4 、 5 、 1 4、5、1 451行覆盖所有的列,即下图蓝色的部分

总结 D a n c i n g L i n k s Dancing Links DancingLinks的求解

  • 函数入口
  • 判断 h e a d . r i g h t  ? = h e a d head.right \space ?= head head.right =head,若成立,输出答案,返回已解决,退出函数
  • 获得 h e a d . r i g h t head.right head.right的元素 C C C
  • 标示元素 C C C
  • 获得元素 C C C所在列的一个元素
  • 标示该元素同行的其他元素所在的列首元素
  • 获得一个简化问题,递归,若返回已解决,则退出函数
  • 若刚刚尝试的不行,回标该元素同行的其他元素所在的列首元素,回标顺序与之前标示的顺序相反
  • 获得元素 C C C所在列的下一个元素,若有,跳转“标示该元素同行的其他元素所在的列首元素”
  • 若没有,回标元素 C C C,返回未解决,退出函数

例题 P4929

代码实现

#include"bits/stdc++.h"
using namespace std;
const int N=250015;//N*M
#define inl inline
#define reg register
#define regi register int
#define PII pair<int,int>
inl int read(void)
{
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch))  f=ch=='-'?-1:f,ch=getchar();
	while(isdigit(ch))   x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return x*f;
}
int l[N],r[N],up[N],down[N],col[N],row[N];//每个点的左右上下指针,以及每个点所在的行列
int head[N];int sz[N];//每行的头结点,每列的节点数
int ans[N];//答案栈
int n,m,cnt;
void build(int m)
{
	for(int i=0;i<=m;i++)	r[i]=i+1,l[i]=i-1,up[i]=down[i]=i;
	r[m]=0,l[0]=m;
	memset(sz,0,sizeof sz);
	cnt=m+1;//已经处理了0行,从第一行开始insert
}
void insert(int R,int C)//在R行C列插入点
{
	sz[C]++;
	row[++cnt]=R,col[cnt]=C;
	up[cnt]=C,down[cnt]=down[C];
	up[down[C]]=cnt,down[C]=cnt;
	if(!head[R])	head[R]=r[cnt]=l[cnt]=cnt;
	else	r[cnt]=head[R],l[cnt]=l[head[R]],r[l[head[R]]]=cnt,l[head[R]]=cnt;
}
void remove(int C)//删除C列的集合
{
	r[l[C]]=r[C],l[r[C]]=l[C];
	for(int i=down[C];i!=C;i=down[i])
		for(int j=r[i];j!=i;j=r[j])
			up[down[j]]=up[j],down[up[j]]=down[j],sz[col[j]]--;
}
void resume(int C)//恢复C列的集合
{
	for(int i=up[C];i!=C;i=up[i])
		for(int j=l[i];j!=i;j=l[j])
			up[down[j]]=down[up[j]]=j,sz[col[j]]++;
	r[l[C]]=C,l[r[C]]=C;
}
bool dance(int dep)
{
	if(r[0]==0)//head.right=head
	{
		for(int i=0;i<dep;i++)	printf("%d ",ans[i]);
		return 1;
	}
	int C=r[0];
	for(int i=r[0];i;i=r[i])	if(sz[i]<sz[C])	C=i;//找到点最少的列(优化)
	remove(C);
	for(int i=down[C];i!=C;i=down[i])
	{
		ans[dep]=row[i];//压入答案栈
		for(int j=r[i];j!=i;j=r[j])	remove(col[j]);
		if(dance(dep+1))	return 1;
		for(int j=l[i];j!=i;j=l[j])	resume(col[j]);
	}
	resume(C);
	return 0;
}
int main(void)
{
	n=read(),m=read();
	int s;
	build(m);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			s=read();
			if(s)	insert(i,j);
		}
	if(!dance(0))	puts("No Solution!");//无解
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值