详解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 ...
随机推荐
- [NOIP11.1模拟赛]补番报告
Preface 昨天开始补某科学的超电磁炮S 感觉今天就好了点,炮姐赛高 T1 一开始一直想欧拉定理&ex欧拉定理,结果估计70分,数组开小了GG,看了正解发现是我学傻了 T2 一看就是数据结 ...
- VBA嵌套if语句
一个If或ElseIf语句可以嵌套在另一个If或ElseIf语句中.内部的If语句是根据最外层的If语句执行的.这使得VBScript能够轻松处理复杂的条件. 语法 以下是VBScript中嵌套的If ...
- mybatisplus 使用案例
案例地址 https://github.com/qixianchuan/SpringBootQD/tree/master/mybatisplus
- Elasticsearch7
elasticsearch 由来 点击进入 elasticsearch 基本概念 点击进入 elasticsearch 安装 点击进入 elasticsearch 增删改查 点击进入 elastics ...
- C# - 配置动态更新
生产中经常会遇到修改配置的情况,但是又需要重启应用程序,是不是有点小烦躁.... 下面了解下在不重启情况下,实现配置更新实时生效 public static void SetConfig(string ...
- FDD-LTE上下行带宽一样的,为什么上下行流量差别这么大
转:https://zhidao.baidu.com/question/923940070377297579.html 虽然FD系统,上下行使用的带宽一样,但是上下行的信号编码效率完全不同.上行信号( ...
- Python使用xlrd、pandas包从Excel读取数据
#coding=utf-8 # pip install xlrd import xlrd def read_from_xls(filepath,index_col_list): #filepath:读 ...
- javascript reduce 前端交互 总计
sum(){ return this.products.reduce((total,next)=>{ return total + next.price * next.aumout},0) } ...
- Usages for IntelliJ & Eclipse
IntelliJ ⌘E Select a recently opened file from the list. ⌘/ (left command + /)⌥⌘/ (right command + / ...
- 创建虚拟目录 http://localhost:1001/ 失败,错误: 无法创建网站
使用vs2015打开一个vs2008项目报错了. 最后网上找到的解决方案, 1,打开编辑xxx.csproj文件: 2,找到节点ProjectExtensions注释掉: 3,重新加载就好了.