初学DLX
前言
\(DLX\),全称\(Dancing\ Links\ X\),即舞蹈链算法。
这是一个十分高效且实用的算法,它主要用于求出精确覆盖问题的一组解。(貌似重复覆盖问题也可以,但我不会\(2333\))
前置基础:十字链表
\(DLX\)这个算法,建立于十字链表的基础之上。
十字链表,就相当于对于一个链表中的节点,我们要维护它上、下、左、右四个方向的相邻节点。
似乎挺好理解的?
而它的作用,就在于可以很好地维护一个矩阵行列的删除,是\(DLX\)这个算法的基础。
什么是精确覆盖问题?
给你一个集合,以及它的若干子集,先要你选择其中一部分子集,使得集合中的每个元素出现且恰好出现一次。
这就是精确覆盖问题。
\(DLX\)的大致思想
我们以每个子集作为矩阵中的行,以每个元素作为矩阵中的列。如果子集中含有某个元素,就将对应行、列上的元素变为\(1\),否则为\(0\)。
例如集合为\(\{1,2,3,4,5,6,7,8\}\),子集为\(\{1,2,3,5,7\},\{1,3,4,8\},\{2,6\},\{4,6,8\},\{5,7\}\),就可以建出这样一个矩阵:
\(1\) | \(2\) | \(3\) | \(4\) | \(5\) | \(6\) | \(7\) | \(8\) | |
---|---|---|---|---|---|---|---|---|
\(1\) | \(1\) | \(1\) | \(1\) | \(0\) | \(1\) | \(0\) | \(1\) | \(0\) |
\(2\) | \(1\) | \(0\) | \(1\) | \(1\) | \(0\) | \(0\) | \(0\) | \(1\) |
\(3\) | \(0\) | \(1\) | \(0\) | \(0\) | \(0\) | \(1\) | \(0\) | \(0\) |
\(4\) | \(0\) | \(0\) | \(0\) | \(1\) | \(0\) | \(1\) | \(0\) | \(1\) |
\(5\) | \(0\) | \(0\) | \(0\) | \(0\) | \(1\) | \(0\) | \(1\) | \(0\) |
然后我们要考虑,每一列只能被选择一次,因此,就能得到\(DLX\)的核心思想:
选择了某一行,就需要删掉所有这一行为\(1\)的列,以及这些列上为\(1\)的行。
以上面的表格为例,假设我们删去了第一行,则需要删除\(1,2,3,5,7\)这\(5\)列,而\(1,2,3,5\)这\(4\)行在这\(5\)列上有\(1\),因此要删去这\(4\)行。
所以最后剩下的表格是这样的:
\(4\) | \(6\) | \(8\) | |
---|---|---|---|
\(4\) | \(1\) | \(1\) | \(1\) |
可以发现,在我们删完第\(4\)行后,整个矩阵就为空了,因此,\(1,4\)是一组合法解。
而如果删完所有行后依然存在某些列没被删,就说明这种选择方式不合法。
具体实现:初始化
一开始,我们要先按照元素总数建好每一列,每个点用一个十字链表存储,上下都指向自己,左右指向相邻节点。(特殊地,最左端指向最右端,最右端指向最左端)
这一过程代码如下:
struct node//十字链表上的一个节点
{
int x,y,u,d,l,r;//坐标(x,y),剩余四个变量分别存储上、下、左、右的相邻节点
I node(CI X=0,CI Y=0,CI U=0,CI D=0,CI L=0,CI R=0):x(X),y(Y),u(U),d(D),l(L),r(R){}//构造函数
}O[N*N+5];
I void Init(CI x)//初始化有x个元素
{
RI i;for(tot=x,i=0;i<=x;++i) O[i]=node(0,i,i,i,i-1,i+1);//对于0~x列,每个元素上下指向自己,左右指向相邻节点
O[O[0].l=x].r=0,memset(lnk,-1,sizeof(lnk));//最左端指向最右端,最右端指向最左端,并初始化lnk为-1
}
具体实现:插入元素
接下来,我们需要把矩阵中为\(1\)的元素一个个插入矩阵。
考虑插入要做些什么。
首先,我们要更新这一列的\(Size\)值加\(1\)(用于搜索剪枝,不然会跑的很慢)。
然后我们为其新建一个节点,更新十字链表的信息。
唯一需要注意的是如果这个节点是该行的第一个节点,则需要将其作为行头节点。
这一过程代码如下:
I void Insert(CI x,CI y)//插入节点
{
++sz[y],O[++tot]=node(x,y,y,O[y].d),O[y].d=O[O[y].d].u=tot,//更新Size,建新节点,更新上下信息
if(~lnk[x]) O[tot].l=lnk[x],O[tot].r=O[lnk[x]].r,O[lnk[x]].r=O[O[lnk[x]].r].l=tot;//如果已有行头节点,更新左右节点
else lnk[x]=O[tot].l=O[tot].r=tot;//否则将其作为行头节点
}
具体实现:删除与还原
由前面我们知道,\(DLX\)的核心思想在于删除行列与还原。
考虑删除一列(还原类似),我们需要删除这一列上值为\(1\)的所有行,因此我们会先枚举列上的每一个元素,然后枚举当前元素所在行的每一元素,将其删去。
代码如下:
I void Delete(CI x)//删除
{
O[O[O[x].l].r=O[x].r].l=O[x].l;//从列中删除
for(RI i=O[x].d;i^x;i=O[i].d) for(RI j=O[i].r;j^i;j=O[j].r)//枚举列中元素和该元素所在的行
O[O[O[j].u].d=O[j].d].u=O[j].u,--sz[O[j].y];//删除
}
I void Regain(CI x)//还原
{
for(RI i=O[x].d;i^x;i=O[i].d) for(RI j=O[i].r;j^i;j=O[j].r)//枚举列中元素和该元素所在的行
O[O[j].u].d=O[O[j].d].u=j,++sz[O[j].y];//还原
O[O[x].l].r=O[O[x].r].l=x;//还原到列中
}
具体实现:\(Dance\)搜索
以上这些其实都只是辅助,真正核心的其实是\(Dance\)这个函数。
首先,我们要判断当前是否已为空矩阵,如果是,则说明已经找到一组合法解,输出即可。
否则,我们找到元素个数最少,即\(Size\)最小的一列,来进行操作(这应该是搜索一个常用剪枝吧)。
操作的过程就是枚举删除这一列的哪一行来删去,记得搜完一行要还原。
代码如下:
I void Dance(CI x)//类似于搜索的一个过程
{
if(!O[0].r) {for(RI i=1;i^x;++i) printf("%d ",res[i]);exit(0);}//如果为空,说明已经找出答案,输出
RI i,j,t=O[0].r;for(i=O[t].r;i;i=O[i].r) sz[t]>sz[i]&&(t=i);//找出Size最小的一列,有助于提高效率
Delete(t);for(i=O[t].d;i^t;i=O[i].d)//枚举选择哪一行
{
for(res[x]=O[i].x,j=O[i].r;j^i;j=O[j].r) Delete(O[j].y);//删掉这一行上的每一列
for(Dance(x+1),j=O[i].l;j^i;j=O[j].l) Regain(O[j].y);//搜索完后还原
}Regain(t);
}
代码(板子)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 500
using namespace std;
int n,m;
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define tn (x<<3)+(x<<1)
#define D isdigit(c=tc())
char c,*A,*B,FI[FS];
public:
I FastIO() {A=B=FI;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
#undef D
}F;
class DancingLinksX//DLX算法
{
private:
int tot,sz[N+5],lnk[N+5],res[N+5];
struct node//十字链表上的一个节点
{
int x,y,u,d,l,r;//坐标(x,y),剩余四个变量分别存储上、下、左、右的相邻节点
I node(CI X=0,CI Y=0,CI U=0,CI D=0,CI L=0,CI R=0):x(X),y(Y),u(U),d(D),l(L),r(R){}//构造函数
}O[N*N+5];
I void Dance(CI x)//类似于搜索的一个过程
{
#define Delete(x)\
{\
O[O[O[x].l].r=O[x].r].l=O[x].l;\
for(RI i=O[x].d;i^x;i=O[i].d) for(RI j=O[i].r;j^i;j=O[j].r)\
O[O[O[j].u].d=O[j].d].u=O[j].u,--sz[O[j].y];\
}//删除
#define Regain(x)\
{\
for(RI i=O[x].d;i^x;i=O[i].d) for(RI j=O[i].r;j^i;j=O[j].r)\
O[O[j].u].d=O[O[j].d].u=j,++sz[O[j].y];\
O[O[x].l].r=O[O[x].r].l=x;\
}//还原
if(!O[0].r) {for(RI i=1;i^x;++i) printf("%d ",res[i]);exit(0);}//如果为空,说明已经找出答案,输出
RI i,j,t=O[0].r;for(i=O[t].r;i;i=O[i].r) sz[t]>sz[i]&&(t=i);//找出Size最小的一列,有助于提高效率
Delete(t);for(i=O[t].d;i^t;i=O[i].d)//枚举选择哪一行
{
for(res[x]=O[i].x,j=O[i].r;j^i;j=O[j].r) Delete(O[j].y);//删掉这一行上的每一列
for(Dance(x+1),j=O[i].l;j^i;j=O[j].l) Regain(O[j].y);//搜索完后还原
}Regain(t);
}
public:
I void Init(CI x)//初始化有x个元素
{
RI i;for(tot=x,i=0;i<=x;++i) O[i]=node(0,i,i,i,i-1,i+1);//对于0~x列,每个元素上下指向自己,左右指向相邻节点
O[O[0].l=x].r=0,memset(lnk,-1,sizeof(lnk));//最左端指向最右端,最右端指向最左端,并初始化lnk为-1
}
I void Insert(CI x,CI y)//插入节点
{
++sz[y],O[++tot]=node(x,y,y,O[y].d),O[y].d=O[O[y].d].u=tot,//更新Size,建新节点,更新上下信息
~lnk[x]?(O[tot].l=lnk[x],O[tot].r=O[lnk[x]].r,O[lnk[x]].r=O[O[lnk[x]].r].l=tot)//如果已有行头节点,更新左右节点
:(lnk[x]=O[tot].l=O[tot].r=tot);//否则将其作为行头节点
}
I void Solve() {Dance(1),puts("No Solution!");}//搜索,若无解输出"No Solution!"
}DLX;
int main()
{
RI i,j,x;for(F.read(n,m),DLX.Init(m),i=1;i<=n;++i)
for(j=1;j<=m;++j) F.read(x),x&&(DLX.Insert(i,j),0);
return DLX.Solve(),0;
}
初学DLX的更多相关文章
- poj 3740 Easy Finding 二进制压缩枚举dfs 与 DLX模板详细解析
题目链接:http://poj.org/problem?id=3740 题意: 是否从0,1矩阵中选出若干行,使得新的矩阵每一列有且仅有一个1? 原矩阵N*M $ 1<= N <= 16 ...
- Dancing Link --- 模板题 HUST 1017 - Exact cover
1017 - Exact cover Problem's Link: http://acm.hust.edu.cn/problem/show/1017 Mean: 给定一个由0-1组成的矩阵,是否 ...
- DDD初学指南
去年就打算总结一下,结果新换的工作特别忙,就迟迟没有认真动手.主要内容是很多初学DDD甚至于学习很长时间的同学没有弄明白DDD是什么,适合什么情况.这世界上没有银弹,抛开了适合的场景孤立的去研究DDD ...
- gulp初学
原文地址:gulp初学 至于gulp与grunt的区别,用过的人都略知一二,总的来说就是2点: 1.gulp的gulpfile.js 配置简单而且更容易阅读和维护.之所以如此,是因为它们的工作方式不 ...
- 初学seaJs模块化开发,利用grunt打包,减少http请求
原文地址:初学seaJs模块化开发,利用grunt打包,减少http请求 未压缩合并的演示地址:demo2 学习seaJs的模块化开发,适合对seajs基础有所了解的同学看,目录结构 js — —di ...
- 初学Vue2.0--基础篇
概述: 鉴于本人初学,使用的编译器是webStorm,需添加对VUE的支持,添加方法可以参考 http://www.jianshu.com/p/142dae4f8b51. 起步: 1. 扎实的 Jav ...
- 初学Python
初学Python 1.Python初识 life is short you need python--龟叔名言 Python是一种简洁优美语法接近自然语言的一种全栈开发语言,由"龟叔&quo ...
- Javascript初学篇章_5(对象)
对象 Javascript是一种面向对象的语言,因此可以使用面向对象的思想来进行javascript程序设计对象就是由一些彼此相关的属性和方法集合在一起而构成的一个数据实体.举个例子,一只猫是个对象, ...
- 初学Objective-C语言需要了解的星星点点
其实大多数开发初学者都有一些相同的特点,可以说是一种“职业病”.Most有其他平台开发基础的初学者,看到Xcode就想摩拳擦掌:看到Interface Builder就想跃跃欲试:而 ...
随机推荐
- 文献综述六:基于JS 技术的电子商品管理系统设计及实现
一.基本信息 标题:基于JS 技术的电子商品管理系统设计及实现 时间:2017 出版源:无线互联科技 文件分类:js技术的研究 二.研究背景 主要对Js下电商管理系统的设计及实现进行了探讨,利用软件工 ...
- Java中多线程并发体系知识点汇总
一.多线程 1.操作系统有两个容易混淆的概念,进程和线程. 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己的独立地址空间,包含程序内容和数据:不同进程的地址空间是互相隔离的:进程拥有各种 ...
- 牛客网Java刷题知识点之UDP协议是否支持HTTP和HTTPS协议?为什么?TCP协议支持吗?
不多说,直接上干货! 福利 => 每天都推送 欢迎大家,关注微信扫码并加入我的4个微信公众号: 大数据躺过的坑 Java从入门到架构师 人工智能躺过的坑 ...
- TOJ 2733 棋盘游戏
Description 小 希和Gardon在玩一个游戏:对一个N*M的棋盘,在格子里放尽量多的一些国际象棋里面的“车”,并且使得他们不能互相攻击,这当然很简单,但是 Gardon限制了只有某些格子才 ...
- 【STM32学习笔记】STM32f407 使用4*4矩阵键盘
作者:李剀 出处:https://www.cnblogs.com/kevin-nancy/ 欢迎转载,但也请保留上面这段声明.谢谢! 写在前面: 这是本人第一次开始写博客,可能写的不是很好,也请大家谅 ...
- unity读取json文件
首先填表 [escel转json]注意,粘贴表之后,需要把最后的空行删掉 http://www.bejson.com/json/col2json/ [json格式化] http://www.bejso ...
- JS中彻底删除json对象组成的数组中的元素
只是分享一个小知识~ 在JS中,对于某个由json对象组成的数组,例如: var test = [{ "a": "1", "b": &quo ...
- attribute和property的区别
DOM元素的attribute和property很容易混倄在一起,分不清楚,两者是不同的东西,但是两者又联系紧密.很多新手朋友,也包括以前的我,经常会搞不清楚. attribute翻译成中文术语为“特 ...
- Spring课程 Spring入门篇 4-4 Spring bean装配(下)之Autowired注解说明3 多选一 qualifier
本节主要讲述以下内容: 1 简述 2 代码演练 2.1 注解qualifier运用 1 简述 1.1 何种情况使用qualifier注解? a 按类型自动装配多个bean实例,可以用@qualifie ...
- laravel vue.js的使用
安装cors 地址:https://github.com/barryvdh/laravel-cors 在Kernel文件中加上 protected $middleware = [ \Barryv ...