什么是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. Go 编译 && 工具

    编译和工具链 Go 的工具链非常丰富,从获取源码.编译.文档.测试.性能分析,到源码格式化.源码提示.重构工具等应有尽有 在 Go 中可以使用测试框架编写单元测试,使用统一的命令行即可测试及输出测试报 ...

  2. 自定义标签之inclusion_tag

    1.在当前app下创建templatetags文件夹 2.在templatetags文件夹下面创建自定义的mytag.py文件 3.在mytag.py文件中的代码 from django.templa ...

  3. 4.JUC之AQS框架

    一.简介 1.AQS AQS是AbstractQueuedSynchronizer的简写,直白的翻译:抽象队列同步器,jdk1.5后出现 Provides a framework for implem ...

  4. Dubbo面试

    DUBBO原理.应用与面经总结 SPI 你是否了解SPI,讲一讲什么是SPI,为什么要使用SPI? SPI具体约定:当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/serv ...

  5. VSCode Git 没有活动的源代码控制提供程序

    主要原因:我以前安装的Git只允许在Git-Bash中运行,需要重新安装Git,选择允许三方软件的那个选项,然后安装Git插件,修改git.path即可. 以下是解决过程中的尝试,记录如下,实际上只需 ...

  6. linux alsa音频中采样率fs、比特率BCLK 、主时钟MCLK关系

    转:https://blog.csdn.net/lugandong/article/details/72468831 一.拿512fs说话: 看图知道采样的位深是32bit(位),左右声道各占了8*3 ...

  7. springboot ElasticSearch 简单的全文检索高亮

    原文:https://segmentfault.com/a/1190000017324038?utm_source=tag-newest 首先引入依赖 <dependency> <g ...

  8. IDEA实用教程(三)

    4. JDK环境的配置 1) 进入JDK配置界面 2) 创建JDK环境 3) 选择本地JDK的安装位置 4) 保存配置 点击右下角的Apply后,再点击OK保存配置

  9. evpp http response_http_code_

    response_http_code_  909 例子代码   evpp  代码内例子 注释 可以读一下

  10. P3205 [HNOI2010]合唱队[区间dp]

    题目描述 为了在即将到来的晚会上有更好的演出效果,作为AAA合唱队负责人的小A需要将合唱队的人根据他们的身高排出一个队形.假定合唱队一共N个人,第i个人的身高为Hi米(1000<=Hi<= ...