名字:
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,C⊆S
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 1、4、5行
言归正传,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 4、5、1,表示该问题的解是第 4 、 5 、 1 4、5、1 4、5、1行覆盖所有的列,即下图蓝色的部分
总结 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,返回未解决,退出函数
代码实现
#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;
}