详解DLX及其应用
什么是DLX?
让我们看看百度百科上的解释:在 计算机科学 中, Dancing Links ,舞蹈链, 也叫 DLX, 是由 Donald Knuth 提出的数据结构,目的是快速实现他的 X算法.X算法是一种递归算法,时间复杂度不确定, 深度优先, 通过回溯寻找精确覆盖问题所有可能的解。有一些著名的精确覆盖问题,包括铺砖块,八皇后问题,数独问题。
X算法
概念
X算法用由0和1组成的矩阵A来表示精确覆盖问题,目标是选出矩阵的若干行,使得其中的1在所有列中出现且仅出现一次。(出自度娘)
实现步骤
1.如果矩阵A为空(没有任何列),则当前局部解即为问题的一个解,返回成功;否则继续。
2.根据一定方法选择第c列。如果某一列中没有1,则返回失败,并去除当前局部解中最新加入的行。
3.选择第r行,使得A[r,c]=1(该步是不确定的)。
4.将第r行加入当前局部解中。
5.对于满足A[r,j]=1的每一列j,从矩阵A中删除所有满足A[i,j]=1的行,最后再删除第j列。
6.对所得比A小的新矩阵递归地执行此算法。
图解
>例如有一个这样的矩阵A:
$$
\mathbf{A} =
\left( \begin{array}{ccc}
{0} \\
{1} \\
{0} \\
{1} \\
{0} \\
{0} \\
\end{array} \right)
$$
ps:例子引用自[grenet奆佬](http://www.cnblogs.com/grenet/p/3145800.html)
这个例子就包含了一个(1,4,5)的精确覆盖解。
1.然后让我们人工模拟一遍X算法,好好体会体会:
最开始首先假定选择第一列:
{0} & {0} & {1} & {0} & {1} & {1} & {0} \\
{ } & { } & { } & { } & { } & { } & { } \\
{ } & { } & { } & { } & { } & { } & { } \\
{ } & { } & { } & { } & { } & { } & { } \\
{ } & { } & { } & { } & { } & { } & { } \\
{ } & { } & { } & { } & { } & { } & { } \\
\end{array} \right)
\]
那么对于第一行有1的列,即(3,5,6),可向下不断延伸,遇到有1的位置,就把该行标记:
\left( \begin{array}{ccc}
{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{array} \right)
\]
2.这样就可以用A矩阵-B矩阵(即删除所标记的行和列),得到一个新的、小一点的矩阵A(即得到一个规模较小的精确覆盖问题):
\left( \begin{array}{ccc}
{1} & {0} & {1} & {1} \\
{1} & {0} & {1} & {0} \\
{0} & {1} & {0} & {1} \\
\end{array} \right)
\]
3.那么根据1,又可进行一下操作:
先选第一列:
\left( \begin{array}{ccc}
{1} & {0} & {1} & {1} \\
{ } & { } & { } & { } \\
{ } & { } & { } & { } \\
\end{array} \right)
\]
那么对于第一行有1的列,即(1,3,4),可向下不断延伸,遇到有1的位置,就把该行标记:
\left( \begin{array}{ccc}
{1} & {0} & {1} & {1} \\
{1} & { } & {1} & {0} \\
{0} & {1} & {0} & {1} \\
\end{array} \right)
\]
4.这样就可以用A矩阵-B矩阵(即删除所标记的行和列),又得到一个新的、小一点的矩阵A(即又得到一个规模较小的精确覆盖问题):
\left( \begin{array}{ccc}
{0}\\
\end{array} \right)
\]
这个时候我们发现当前A矩阵不为空,也没有一列有1(既无法继续操作)
则这个时候说明之前走出了错误的一步,就需要我们回溯——
5.那么根据步骤就回溯到3:
\left( \begin{array}{ccc}
{1} & {0} & {1} & {1} \\
{1} & {0} & {1} & {0} \\
{0} & {1} & {0} & {1} \\
\end{array} \right)
\]
这个时候就不能尝试第一行,那我们就标记第二行,并按照之前的方法扩展:
\left( \begin{array}{ccc}
{1} & {0} & {1} & {1} \\
{1} & {0} & {1} & {0} \\
{0} & { } & {0} & { } \\
\end{array} \right)
\]
6.那么由4,同理我们可得:
\left( \begin{array}{ccc}
{1} & {1} \\
\end{array} \right)
\]
那么继续上面的步骤,显然整个矩阵最后就可以被缩减完。
7.由此,我们就得到了那组解(1,4,5)
DLX算法
那为什么还要用DLX算法呢?直接用X算法不好吗?
根据我们刚才的运行过程,
如果过程中有大量的回溯和标记过程,那我们如果用数组存储之前的信息,显然是不可能的,~妥妥的MLE没商量
这个时候我们就要隆重请出舞蹈链的X算法(即DLX)
舞蹈链怎样实现
舞蹈链,即为一个双向十字循环链表,**即每个点都与上下左右四个点连有一条双向指针**
**(第一排的up指向最后一排,最后一排的down指向第一排;第一列的left指向最后一列,最后一列的right指向第一列)**
**重点:因为是链表,所以我们需要一个初始节点来建表**
>1.那么最开始初始化的时候,我们将第一行的指针指好:
```cpp
templateinline void init(TP n,TP m)
{
F1(i,0,m)
{
L[i]=i-1,R[i]=i+1;
U[i]=D[i]=i;
}
L[0]=m,R[m]=0,cnt=m;
//cnt即已有节点,H[]即链表表头
memset(H,-1,sizeof H);
return;
}
```
2.对输入矩阵扫描,对于有1的点进行插入操作:
这样我们就只在1与1之间建链,对于X算法中挨个挨个去扩展来找1就要快得多:
template<typename TP>inline void push(TP r,TP c)
{
U[++cnt]=c,D[cnt]=D[c];
U[D[c]]=cnt,D[c]=cnt;
row[cnt]=r,col[cnt]=c;
if(H[r]!=-1)
{
R[cnt]=R[H[r]],L[R[H[r]]]=cnt;
L[cnt]=H[r],R[H[r]]=cnt;
}
else H[r]=L[cnt]=R[cnt]=cnt;
return;
}
3.对于最关键的删除/回溯操作
因为只将有1的点加入链表,所以直接扫就行
值得一提的是,我们在删除的时候(就是从当前所选列扩展的时候),我们只是把"对应列"有1的行与整个链表“分开”,而这一行元素之间的关系并没有破坏,这样回溯的时候就相当容易,只需反着操作一遍即可。
代码如下:
//删除操作:
template<typename TP>inline void del(TP c)
{
L[R[c]]=L[c],R[L[c]]=R[c];
for(TP i=D[c];i!=c;i=D[i])
for(TP j=R[i];j!=i;j=R[j])
U[D[j]]=U[j],D[U[j]]=D[j];
return;
}
//回溯操作:
template<typename TP>inline void reback(TP c)
{
for(TP i=U[c];i!=c;i=U[i])
for(TP j=L[i];j!=i;j=L[j])
U[D[j]]=D[U[j]]=j;
L[R[c]]=R[L[c]]=c;
return;
}
4.接下来就是整个舞蹈链过程中最美的地方:
template<typename TP>inline bool dancing(TP dep)
{
if(R[0]==0)
{
tot=dep;
return true;
}
TP c=R[0];del(c);
for(TP i=D[c];i!=c;i=D[i])
{
ans[dep]=row[i];//记录答案
for(TP j=R[i];j!=i;j=R[j]) del(col[j]);
if(dancing(dep+1)) return true;
for(TP j=L[i];j!=i;j=L[j]) ret(col[j]);
}
ret(c); //这个地方一定要记得回溯!!
return false;
}
例题:
[LuoguP4929 【模板】舞蹈链(DLX)](https://www.luogu.org/problemnew/show/P4929)
完整代码(建议先自己写一遍再看):
```cpp
#include
#include
#define rg register int
#define I inline int
#define V inline void
#define ll long long
#define db double
#define B inline bool
#define F1(i,a,b) for(rg i=a;i=b;--i)
#define ed putchar('\n')
#define bl putchar(' ')
using namespace std;
#define getchar()(p1==p2&&(p2=(p1=buf)+fread(buf,1,1V read(TP &x)
{
TP f=1;x=0;register char c=getchar();
for(;c'9';c=getchar()) if(c=='-') f=-1;
for(;c>='0'&&cV print(TP x)
{
if(x9) print(x/10);
putchar(x%10+'0');
}
const int N=250005;
int n,m,a,cnt;
struct Dancing_Links_X{
int U[N],D[N],L[N],R[N],col[N],row[N],ans[N],H[N];
V init()
{
F1(i,0,m)
{
L[i]=i-1,R[i]=i+1;
U[i]=D[i]=i;
}
L[0]=m,R[m]=0,cnt=m;
memset(H,-1,sizeof H);
return;
}
templateV push(TP r,TP c)
{
U[++cnt]=c,D[cnt]=D[c];
U[D[c]]=cnt,D[c]=cnt;
row[cnt]=r,col[cnt]=c;
if(H[r]!=-1)
{
L[cnt]=H[r],R[cnt]=R[H[r]];
L[R[H[r]]]=cnt,R[H[r]]=cnt;
}
else H[r]=L[cnt]=R[cnt]=cnt;
return;
}
templateV del(TP c)
{
L[R[c]]=L[c],R[L[c]]=R[c];
for(TP i=D[c];i!=c;i=D[i])
for(TP j=R[i];j!=i;j=R[j])
U[D[j]]=U[j],D[U[j]]=D[j];
return;
}
templateV reback(TP c)
{
for(TP i=U[c];i!=c;i=U[i])
for(TP j=L[i];j!=i;j=L[j])
U[D[j]]=D[U[j]]=j;
L[R[c]]=R[L[c]]=c;
return;
}
templateB dancing(TP tot)
{
if(R[0]==0)
{
F1(i,0,tot-1) print(ans[i]),bl;
return true;
}
TP c=R[0];del(c);
for(TP i=D[c];i!=c;i=D[i])
{
ans[tot]=row[i];
for(TP j=R[i];j!=i;j=R[j]) del(col[j]);
if(dancing(tot+1)) return true;
for(TP j=L[i];j!=i;j=L[j]) reback(col[j]);
}
reback(c);
return false;
}
}DLX;
int main()
{
read(n),read(m),DLX.init();
F1(i,1,n)
F1(j,1,m)
{
read(a);
if(a) DLX.push(i,j);
}
if(!DLX.work(0)) puts("No Solution!");
return 0;
}
```
优化
当然,如果你完全按照上面这么打一定会~TLE(~~hhh~~)
这个地方还有一个优化,就是我们在"dancing"过程中,无论用什么方法选择列最终都可以得到解,但有的方法效率明显较高。
为减少迭代次数,我们可以每次都选取1最少的列。
进行这个操作我们只需再定义一个数组s[]
在"push“中加上这样一句:
++s[c];
将"del"部分改成:
U[D[j]]=U[j],D[U[j]]=D[j],--s[col[j]];
将"reback"部分改成:
U[D[j]]=D[U[j]]=j,++s[col[j]];
将"dancing"部分改成:
TP c=R[0];
for(TP i=c;i!=0;i=R[i]) if(s[i]<s[c]) c=i;
del(c);
DLX应用(解数独问题)(有空再写)
为什么可以用舞蹈链做
详解DLX及其应用的更多相关文章
- Dancing Link 详解(转载)
Dancing Link详解: http://www.cnblogs.com/grenet/p/3145800.html Dancing Link求解数独: http://www.cnblogs.co ...
- 五、RabbitMQ Java Client基本使用详解
Java Client的5.x版本系列需要JDK 8,用于编译和运行.在Android上,仅支持Android 7.0或更高版本.4.x版本系列支持7.0之前的JDK 6和Android版本. 加入R ...
- Linq之旅:Linq入门详解(Linq to Objects)
示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集 ...
- 架构设计:远程调用服务架构设计及zookeeper技术详解(下篇)
一.下篇开头的废话 终于开写下篇了,这也是我写远程调用框架的第三篇文章,前两篇都被博客园作为[编辑推荐]的文章,很兴奋哦,嘿嘿~~~~,本人是个很臭美的人,一定得要截图为证: 今天是2014年的第一天 ...
- EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用详解
前言 我比较喜欢安静,大概和我喜欢研究和琢磨技术原因相关吧,刚好到了元旦节,这几天可以好好学习下EF Core,同时在项目当中用到EF Core,借此机会给予比较深入的理解,这里我们只讲解和EF 6. ...
- Java 字符串格式化详解
Java 字符串格式化详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 文中如有纰漏,欢迎大家留言指出. 在 Java 的 String 类中,可以使用 format() 方法 ...
- Android Notification 详解(一)——基本操作
Android Notification 详解(一)--基本操作 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Notification 文中如有纰 ...
- Android Notification 详解——基本操作
Android Notification 详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 前几天项目中有用到 Android 通知相关的内容,索性把 Android Notificatio ...
- Git初探--笔记整理和Git命令详解
几个重要的概念 首先先明确几个概念: WorkPlace : 工作区 Index: 暂存区 Repository: 本地仓库/版本库 Remote: 远程仓库 当在Remote(如Github)上面c ...
随机推荐
- Git pull记住密码
在使用https git拉取代码时,每次git pull的时候都会让输入用户名和密码 进入项目目录 命令:git config --global credential.helper store 然后会 ...
- werkzeug/routing.py-Rule源码分析
Rule类主要用来定义和表示一个URL的模式.主要定义了一些关键字参数,用来改变url的行为.例如:这个url可以接收的请求方法,url的子域名,默认路径,端点名称,是否强制有斜杠在末尾等等 在最开始 ...
- MVC-Session
1.什么是Session? Session即会话,是指一个用户在一段时间内对某一个站点的一次访问. Session对象在.NET中对应HttpSessionState类,表示"会话状态& ...
- Android笔记 (一) 第一次接触Android
1.下载android SDK 访问http://developer.android.com/ → GET THE SDK → Installing the Android SDK → STAN ...
- CSS之特性相关
一.css的继承性与层叠性 继承性: 面向对象语言都会存在继承的概念,在面向对象语言中,继承的特点:继承了父类的属性和方法.那么我们现在主要研究css,css就是在设置属性的.不会牵扯到方法的层面. ...
- CIP 协议安全扫盲
- Java8新特性之重复注解(repeating annotations)
一.什么是重复注解 允许在同一申明类型(类,属性,或方法)的多次使用同一个注解 二.一个简单的例子java 8之前也有重复使用注解的解决方案,但可读性不是很好,比如下面的代码: 复制代码代码如下: p ...
- Java面试题及答案解析
面向对象编程(OOP) Java是一个支持并发.基于类和面向对象的计算机编程语言.下面列出了面向对象软件开发的优点: 代码开发模块化,更易维护和修改. 代码复用. 增强代码的可靠性和灵活性. 增加代码 ...
- Codeforces Round #499(Div2) C. Fly (二分精度)
http://codeforces.com/contest/1011/problem/C 题目 这是一道大水题! 仅以此题解作为我这个蒟蒻掉分的见证 #include<iostream> ...
- 记一个VS连接过程中找不到cpp的解决方法
在新增几个qt页面时,发现原来没动的几个cpp 连接报错了,错误均是qt的相关文件找不到 应该是moc文件没有生产或者没有被包含进工程.我想着既然我没动,应该不会是moc的原因,就在其他方向解决了很久 ...