详解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 ... 
随机推荐
- Spark机器学习基础-无监督学习
			0.K-means from __future__ import print_function from pyspark.ml.clustering import KMeans#硬聚类 #from p ... 
- 编写Postgres扩展之三:调试
			原文:http://big-elephants.com/2015-10/writing-postgres-extensions-part-iii/ 编译:Tacey Wong 在上一篇关于编写Post ... 
- The Art Of Loving
			The Art Of Loving 来源 https://www.zhihu.com/question/23720541 ----------------------------- 茫然的蒲公英 有书 ... 
- Springboot笔记01——Springboot简介
			一.什么是微服务 在了解Springboot之前,首先我们需要了解一下什么是微服务. 微服务是一种架构风格(服务微化),是martin fowler在2014年提出来的.微服务简单地说就是:一个应用应 ... 
- Feign的理解
			Feign是什么? Feign是一个http请求调用的轻量级框架,也可以说是声明式WebService客户端 Feign的作用 可以以Java接口注解的方式调用Http请求,它使java调用Http请 ... 
- 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 ... 
- ceph集群部署(基于jewel版)
			环境 两个节点:ceph1.ceph2 ceph1: mon.mds.osd.0.osd.1 ceph2: osd.2.osd.3 网络配置: ceph1: 管理网络,eth0,10.0.0.20 存 ... 
- git命令——git log
			功能 在提交了若干更新,又或者克隆了某个项目之后,你也许想回顾下提交历史. 完成这个任务最简单而又有效的方法是 使用git log 命令. 参数 不带任何参数 $ git log commit ca8 ... 
- Python + Selenium 主要实现的功能
			selenium 技术 元素定位的几种方法 WebDriver API ,selenium IDE,selenium grid python 技术 函数.类.方法: 读写文件, unitest单元测试 ... 
- Linux网络编程综合运用之MiniFtp实现(四)
			从今天开始,正式进入MiniFtp的代码编写阶段了,好兴奋,接下来很长一段时间会将整个实现过程从无到有一点点实现出来,达到综合应用的效果,话不多说正入正题: 这节主要是将基础代码框架搭建好,基于上节介 ... 
