[学习笔记] 舞蹈链(DLX)入门
“在一个全集\(X\)中若干子集的集合为\(S\),精确覆盖(\(\boldsymbol{Exact~Cover}\))是指,\(S\)的子集\(S*\),满足\(X\)中的每一个元素在\(S*\)中恰好出现一次。在计算机科学中,精确覆盖问题指找出这样的一种覆盖,或证明其不存在。”
\(0x01\) 精准覆盖问题
……其实是一种决策问题,给定\(n\)行长度为\(m\)的\(0,1\)序列,要求选出一些行,使得每一列有且仅有一个\(1\),这就是精准覆盖问题。
诚然,我搜索贼菜,所以暂且不考虑爆搜,引进一种叫做“X算法”的东西,其本质上是每次选取一行,之后删掉所有与这行冲突的行,同时删掉与这行冲突的列,成为一个更小的矩阵,迭代下去。如果什么时候删没了,就说明是一种可行解;否则恢复原来的状态。
我们思考这种简洁做法的流程,发现朴素的删除与恢复无非就是将矩阵的这一个元素由\(0\)或\(1\)赋值成\(-1\),记录一下状态回溯的时候再赋值回去,整个过程十分地漫长且繁复。而所谓所谓的”舞蹈链算法\(\rm{DLX~(Dancing-Links ~X ~Algorithm)}\)“算法则是专门用来加速这一过程。
在本人看来,\(\rm{DLX}\)更像是一种包装好的数据结构,一种加速措施,能更好的让爆搜达到其理论复杂度(所以本质上还是爆搜XD)……不过说实话“像翩翩起舞的舞者”我倒是看不出来…我觉得更像是一对牛仔裤上拉链,拉来拉去的那种感觉……
诶,什么时候我的Preface开始这么意识流了啊
\(0x02\) \(\text{Dancing-Links}\)
其实算法的本质就是链表,这玩意儿插入删除都是\(\Theta(1)\)的。我们考虑建立一个十字循环链表,即每个元素在链表里是四联通的,并且左右成环、上下成环,目的是方便知道某些操作该什么时候停止。本质上来讲,一个求解矩阵(此处代指上文提到的\(n\)行\(0,1\)序列)初始的\(\text{Dancing-Links}\) 共有\(\text{1+m+Count('1')}\) 个元素,其中\(Count('1')\)指矩阵中\(1\)的个数。
前\(\text{m+1}\)个元素,大概就是列标元素(\(m\)个)左右连成一片,最左边的\(0\)号元素用来判断是否\(\text{worked-out}\)整个矩阵,和所有列标元素串成一条左右连通的链表。然后剩下的的元素就是真实存在的元素…该怎么连怎么连那种感觉…
那么每个元素记录\(6\)个值,上下左右和行标列标。
struct Node{
int l, r, u, d, co, ro ;
}B[MAX << 1] ;
初始化
inline void Init(){
cin >> N >> M ;
for (int i = 0 ; i <= M ; ++ i) B[i].l = i - 1, B[i].r = i + 1, B[i].u = B[i].d = i ;
B[M].r = 0, B[0].l = M, cnt = M, memset(Ro, -1, sizeof(Ro)) ;
}
其中\(R_o[]\)数组记录每一行的第一个元素(第一个加进来的元素
然后Insert
函数用于插入……毕竟是链表嘛,就要有个链表的样子
inline void Insert(int R, int C){
Cs[C] ++, B[++ cnt].ro = R, B[cnt].co = C ;
B[cnt].u = C, B[cnt].d = B[C].d, B[C].d = B[B[C].d].u = cnt ;
if (Ro[R] < 0) Ro[R] = B[cnt].l = B[cnt].r = cnt ;
else B[cnt].l = B[Ro[R]].l, B[cnt].r = Ro[R], B[Ro[R]].l = B[B[Ro[R]].l].r = cnt ;
}
然后\(C_s[]\)数组用来记录每一列的元素个数,用来剪枝。
然后就是删除和恢复,都是以列为参数的函数,也都是很平凡的操作。
inline void Del(int C){
B[B[C].l].r = B[C].r, B[B[C].r].l = B[C].l ;
for (int i = B[C].d ; i != C ; i = B[i].d)
for (int j = B[i].r ; j != i ; j = B[j].r)
B[B[j].d].u = B[j].u, B[B[j].u].d = B[j].d, Cs[B[j].co] -- ;
}
inline void Back(int C){
for (int i = B[C].u ; i != C ; i = B[i].u)
for (int j = B[i].l ; j != i ; j = B[j].l)
B[B[j].d].u = j, B[B[j].u].d = j, Cs[B[j].co] ++ ;
B[B[C].l].r = C, B[B[C].r].l = C ;
}
然后是主函数
bool dance(int step){
if (!B[0].r){ return (bool)(ans = step) ; } int now_c = B[0].r ;
for (int i = B[0].r ; i ; i = B[i].r)
now_c = Cs[i] < Cs[now_c] ? i : now_c ;
Del(now_c) ;
for (int i = B[now_c].d ; i != now_c ; i = B[i].d) {
Ans[step] = B[i].ro ; for(int j = B[i].r ; j != i ; j = B[j].r) Del(B[j].co) ;
if (dance(step + 1)) return 1 ; for(int j = B[i].l ; j != i ; j = B[j].l) Back(B[j].co) ;
}
Back(now_c) ; return 0 ;
}
有个小剪枝,就是刚才说的\(C_s[]\)。如果每次从含有最少\(1\)的那一列开始删,似乎可以快好几倍。
我发现整理算法的文章写起来真是难受啊,还是意识流比较管用。
最后是全部的程序(\(Luogu4929\)):
#include <cstdio>
#include <cstring>
#include <iostream>
#define I_used_to_rule_the_world___Seas_would_rise_when_I_gave_the_word___Now_in_the_morning_I_sleep_alone___Sweep_the_streets_I_used_to_own Init
#define I_used_to_roll_the_dice___Feel_the_fear_in_my_enemy_s_eyes___Listen_as_the_crowd_would_sing_Now_the_old_king_is_dead__Long_live_the_king Done
#define One_minute_I_held_the_key__Next_the_walls_were_closed_on_me__And_I_discovered_that_my_castles_stand__Upon_pillars_of_salt_pillars_of_sand work
#define MAXN 520
#define MAX 30010
using namespace std ;
struct Node{
int l, r, u, d, co, ro ;
}B[MAX << 1] ; int cnt, ans ;
int N, M, Ans[MAX], Ro[MAX], Cs[MAX] ;
inline void Init(){
cin >> N >> M, memset(Ro, -1, sizeof(Ro)) ;
for (int i = 0 ; i <= M ; ++ i) B[i].l = i - 1, B[i].r = i + 1, B[i].u = B[i].d = i ;
B[M].r = 0, B[0].l = M, cnt = M ;
}
inline void Insert(int R, int C){
Cs[C] ++, B[++ cnt].ro = R, B[cnt].co = C ;
B[cnt].u = C, B[cnt].d = B[C].d, B[C].d = B[B[C].d].u = cnt ;
if (Ro[R] < 0) Ro[R] = B[cnt].l = B[cnt].r = cnt ;
else B[cnt].l = B[Ro[R]].l, B[cnt].r = Ro[R], B[Ro[R]].l = B[B[Ro[R]].l].r = cnt ;
}
inline void Del(int C){
B[B[C].l].r = B[C].r, B[B[C].r].l = B[C].l ;
for (int i = B[C].d ; i != C ; i = B[i].d)
for (int j = B[i].r ; j != i ; j = B[j].r)
B[B[j].d].u = B[j].u, B[B[j].u].d = B[j].d, Cs[B[j].co] -- ;
}
inline void Back(int C){
for (int i = B[C].u ; i != C ; i = B[i].u)
for (int j = B[i].l ; j != i ; j = B[j].l)
B[B[j].d].u = j, B[B[j].u].d = j, Cs[B[j].co] ++ ;
B[B[C].l].r = C, B[B[C].r].l = C ;
}
bool dance(int step){
if (!B[0].r){ return (bool)(ans = step) ; } int now_c = B[0].r ;
for (int i = B[0].r ; i ; i = B[i].r)
now_c = Cs[i] < Cs[now_c] ? i : now_c ;
Del(now_c) ;
for (int i = B[now_c].d ; i != now_c ; i = B[i].d) {
Ans[step] = B[i].ro ; for(int j = B[i].r ; j != i ; j = B[j].r) Del(B[j].co) ;
if (dance(step + 1)) return 1 ; for(int j = B[i].l ; j != i ; j = B[j].l) Back(B[j].co) ;
}
Back(now_c) ; return 0 ;
}
void Done(){
int i, j, k ;
for (i = 1 ; i <= N ; ++ i)
for (j = 1 ; j <= M ; ++ j)
{ cin >> k ; if (k) Insert(i, j) ;}
}
inline bool work(){
if (!dance(0)) return puts("No Solution!") ;
for (int i = 0 ; i < ans ; ++ i) printf("%d ", Ans[i]) ;
}
int main(){
I_used_to_rule_the_world___Seas_would_rise_when_I_gave_the_word___Now_in_the_morning_I_sleep_alone___Sweep_the_streets_I_used_to_own() ;
I_used_to_roll_the_dice___Feel_the_fear_in_my_enemy_s_eyes___Listen_as_the_crowd_would_sing_Now_the_old_king_is_dead__Long_live_the_king() ;
One_minute_I_held_the_key__Next_the_walls_were_closed_on_me__And_I_discovered_that_my_castles_stand__Upon_pillars_of_salt_pillars_of_sand() ;
return 0 ; /*
I hear Jerusalem bells are ringing Roman Cavalry choirs are singing
Be my mirror my sword and shield My missionaries in a foreign field
For some reason I can't explain Once you know there was never'
Never an honest word That was when I ruled the world
*/
}
[学习笔记] 舞蹈链(DLX)入门的更多相关文章
- jQuery学习笔记 - 基础知识扫盲入门篇
jQuery学习笔记 - 基础知识扫盲入门篇 2013-06-16 18:42 by 全新时代, 11 阅读, 0 评论, 收藏, 编辑 1.为什么要使用jQuery? 提供了强大的功能函数解决浏览器 ...
- js学习笔记:webpack基础入门(一)
之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...
- Oracle RAC学习笔记:基本概念及入门
Oracle RAC学习笔记:基本概念及入门 2010年04月19日 10:39 来源:书童的博客 作者:书童 编辑:晓熊 [技术开发 技术文章] oracle 10g real applica ...
- Linux内核学习笔记-1.简介和入门
原创文章,转载请注明:Linux内核学习笔记-1.简介和入门 By Lucio.Yang 部分内容来自:Linux Kernel Development(Third Edition),Robert L ...
- 【转载】【时序约束学习笔记1】Vivado入门与提高--第12讲 时序分析中的基本概念和术语
时序分析中的基本概念和术语 Basic concept and Terminology of Timing Analysis 原文标题及网址: [时序约束学习笔记1]Vivado入门与提高--第12讲 ...
- 舞蹈链 DLX
欢迎访问——该文出处-博客园-zhouzhendong 去博客园看该文章--传送门 舞蹈链是一个非常玄学的东西…… 问题模型 精确覆盖问题:在一个01矩阵中,是否可以选出一些行的集合,使得在这些行的集 ...
- 卷积神经网络(CNN)学习笔记1:基础入门
卷积神经网络(CNN)学习笔记1:基础入门 Posted on 2016-03-01 | In Machine Learning | 9 Comments | 14935 Vie ...
- Java IO学习笔记八:Netty入门
作者:Grey 原文地址:Java IO学习笔记八:Netty入门 多路复用多线程方式还是有点麻烦,Netty帮我们做了封装,大大简化了编码的复杂度,接下来熟悉一下netty的基本使用. Netty+ ...
- React学习笔记(一)- 入门笔记
React入门指南 作者:狐狸家的鱼 本文链接:React学习笔记 GitHub:sueRimn 1.组件内部状态state的修改 修改组件的每个状态,组件的render()方法都会再次运行.这样就可 ...
随机推荐
- 从Python安装到语法基础,这才是初学者都能懂的爬虫教程
Python和PyCharm的安装:学会Python和PyCharm的安装方法 变量和字符串:学会使用变量和字符串的基本用法 函数与控制语句:学会Python循环.判断语句.循环语句和函数的使用 Py ...
- 对js的有感而发
1.什么是JavaScript?他是一个脚本语言,也是一种解释性语言,也是一种弱类型语言.2,当我们学习JavaScript时我们肯定要知道,js的组成是什么? 应该怎么用?这些是最基础的.js的组成 ...
- nodejs环境下的socket通信
结构: socket是应用层和传输层的桥梁.(传输层之上的协议所涉及的数据都是在本机处理的,并没进入网络中) 涉及数据: socket所涉及的数据是报文,是明文. 作用: 建立长久链接,供网络上的两个 ...
- 在 ASP.NET Core 中使用 ApplicationPart 的简单示例
1. 项目截图: 2. 代码 <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFra ...
- Installation request for topthink/think-captcha ^3.0 -> satisfiable by topthink/think-captcha[v3.0.0].
ThinkPHP5.1安装图形验证码的时候报错: Problem 1 - Installation request for topthink/think-captcha ^3.0 -> sati ...
- Netty—TCP的粘包和拆包问题
一.前言 虽然TCP协议是可靠性传输协议,但是对于TCP长连接而言,对于消息发送仍然可能会发生粘贴的情形.主要是因为TCP是一种二进制流的传输协议,它会根据TCP缓冲对包进行划分.有可能将一个大数据包 ...
- WPF中DataGrid在没有数据的时候也可以显示水平滚动条
今天做项目中遇到个问题,就是页面加载后默认DataGrid是不加载数据的,但是DataGrid的列很多,就导致了运行效果上,此窗口的DataGrid没有水平滚动条,类似图片的效果. 经过百度和摸索,使 ...
- 用Python在Excel里画出蒙娜丽莎
前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者: 麦麦麦造 PS:如有需要Python学习资料的小伙伴可以加点击下方链 ...
- MySQL学习——查询表里的数据
MySQL学习——查询表里的数据 摘要:本文主要学习了使用DQL语句查询表里数据的方法. 数据查询 语法 select [distinct] 列1 [as '别名1'], ..., 列n [as '别 ...
- CAD制图初学入门教程:怎么在CAD中绘制箭头
在接触CAD的时候大家有没有和小编一样感觉无所适从,所以下面就来和大家分享一个CAD制图初学入门教程,在CAD中绘制箭头.在CAD图形上进行标注内容的时候一般都会使用箭头来进行指示,那具体怎么在CAD ...