什么是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算法,好好体会体会:

最开始首先假定选择第一列:

\[\left( \begin{array}{ccc}
{0} & {0} & {1} & {0} & {1} & {1} & {0} \\
{ } & { } & { } & { } & { } & { } & { } \\
{ } & { } & { } & { } & { } & { } & { } \\
{ } & { } & { } & { } & { } & { } & { } \\
{ } & { } & { } & { } & { } & { } & { } \\
{ } & { } & { } & { } & { } & { } & { } \\
\end{array} \right)
\]

那么对于第一行有1的列,即(3,5,6),可向下不断延伸,遇到有1的位置,就把该行标记:

\[\mathbf{B} =
\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(即得到一个规模较小的精确覆盖问题):

\[\mathbf{A} =
\left( \begin{array}{ccc}
{1} & {0} & {1} & {1} \\
{1} & {0} & {1} & {0} \\
{0} & {1} & {0} & {1} \\
\end{array} \right)
\]

3.那么根据1,又可进行一下操作:

先选第一列:

\[\mathbf{A} =
\left( \begin{array}{ccc}
{1} & {0} & {1} & {1} \\
{ } & { } & { } & { } \\
{ } & { } & { } & { } \\
\end{array} \right)
\]

那么对于第一行有1的列,即(1,3,4),可向下不断延伸,遇到有1的位置,就把该行标记:

\[\mathbf{B} =
\left( \begin{array}{ccc}
{1} & {0} & {1} & {1} \\
{1} & { } & {1} & {0} \\
{0} & {1} & {0} & {1} \\
\end{array} \right)
\]

4.这样就可以用A矩阵-B矩阵(即删除所标记的行和列),又得到一个新的、小一点的矩阵A(即又得到一个规模较小的精确覆盖问题):

\[\mathbf{A} =
\left( \begin{array}{ccc}
{0}\\
\end{array} \right)
\]

这个时候我们发现当前A矩阵不为空,也没有一列有1(既无法继续操作)

则这个时候说明之前走出了错误的一步,就需要我们回溯——

5.那么根据步骤就回溯到3:

\[\mathbf{A} =
\left( \begin{array}{ccc}
{1} & {0} & {1} & {1} \\
{1} & {0} & {1} & {0} \\
{0} & {1} & {0} & {1} \\
\end{array} \right)
\]

这个时候就不能尝试第一行,那我们就标记第二行,并按照之前的方法扩展:

\[\mathbf{B} =
\left( \begin{array}{ccc}
{1} & {0} & {1} & {1} \\
{1} & {0} & {1} & {0} \\
{0} & { } & {0} & { } \\
\end{array} \right)
\]

6.那么由4,同理我们可得:

\[\mathbf{A} =
\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及其应用的更多相关文章

  1. Dancing Link 详解(转载)

    Dancing Link详解: http://www.cnblogs.com/grenet/p/3145800.html Dancing Link求解数独: http://www.cnblogs.co ...

  2. 五、RabbitMQ Java Client基本使用详解

    Java Client的5.x版本系列需要JDK 8,用于编译和运行.在Android上,仅支持Android 7.0或更高版本.4.x版本系列支持7.0之前的JDK 6和Android版本. 加入R ...

  3. Linq之旅:Linq入门详解(Linq to Objects)

    示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集 ...

  4. 架构设计:远程调用服务架构设计及zookeeper技术详解(下篇)

    一.下篇开头的废话 终于开写下篇了,这也是我写远程调用框架的第三篇文章,前两篇都被博客园作为[编辑推荐]的文章,很兴奋哦,嘿嘿~~~~,本人是个很臭美的人,一定得要截图为证: 今天是2014年的第一天 ...

  5. EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用详解

    前言 我比较喜欢安静,大概和我喜欢研究和琢磨技术原因相关吧,刚好到了元旦节,这几天可以好好学习下EF Core,同时在项目当中用到EF Core,借此机会给予比较深入的理解,这里我们只讲解和EF 6. ...

  6. Java 字符串格式化详解

    Java 字符串格式化详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 文中如有纰漏,欢迎大家留言指出. 在 Java 的 String 类中,可以使用 format() 方法 ...

  7. Android Notification 详解(一)——基本操作

    Android Notification 详解(一)--基本操作 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Notification 文中如有纰 ...

  8. Android Notification 详解——基本操作

    Android Notification 详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 前几天项目中有用到 Android 通知相关的内容,索性把 Android Notificatio ...

  9. Git初探--笔记整理和Git命令详解

    几个重要的概念 首先先明确几个概念: WorkPlace : 工作区 Index: 暂存区 Repository: 本地仓库/版本库 Remote: 远程仓库 当在Remote(如Github)上面c ...

随机推荐

  1. Spark机器学习基础-无监督学习

    0.K-means from __future__ import print_function from pyspark.ml.clustering import KMeans#硬聚类 #from p ...

  2. 编写Postgres扩展之三:调试

    原文:http://big-elephants.com/2015-10/writing-postgres-extensions-part-iii/ 编译:Tacey Wong 在上一篇关于编写Post ...

  3. The Art Of Loving

    The Art Of Loving 来源 https://www.zhihu.com/question/23720541 ----------------------------- 茫然的蒲公英 有书 ...

  4. Springboot笔记01——Springboot简介

    一.什么是微服务 在了解Springboot之前,首先我们需要了解一下什么是微服务. 微服务是一种架构风格(服务微化),是martin fowler在2014年提出来的.微服务简单地说就是:一个应用应 ...

  5. Feign的理解

    Feign是什么? Feign是一个http请求调用的轻量级框架,也可以说是声明式WebService客户端 Feign的作用 可以以Java接口注解的方式调用Http请求,它使java调用Http请 ...

  6. 2743711 - Possible Unexpected Results When Using Query With an ORDER BY Clause on a Rowstore Table With a Parallelized Search on a Cpbtree-Type Index

    2743711 - Possible Unexpected Results When Using Query With an ORDER BY Clause on a Rowstore Table W ...

  7. ceph集群部署(基于jewel版)

    环境 两个节点:ceph1.ceph2 ceph1: mon.mds.osd.0.osd.1 ceph2: osd.2.osd.3 网络配置: ceph1: 管理网络,eth0,10.0.0.20 存 ...

  8. git命令——git log

    功能 在提交了若干更新,又或者克隆了某个项目之后,你也许想回顾下提交历史. 完成这个任务最简单而又有效的方法是 使用git log 命令. 参数 不带任何参数 $ git log commit ca8 ...

  9. Python + Selenium 主要实现的功能

    selenium 技术 元素定位的几种方法 WebDriver API ,selenium IDE,selenium grid python 技术 函数.类.方法: 读写文件, unitest单元测试 ...

  10. Linux网络编程综合运用之MiniFtp实现(四)

    从今天开始,正式进入MiniFtp的代码编写阶段了,好兴奋,接下来很长一段时间会将整个实现过程从无到有一点点实现出来,达到综合应用的效果,话不多说正入正题: 这节主要是将基础代码框架搭建好,基于上节介 ...