舞蹈链(DLX)

Tags:搜索

作业部落

评论地址


一、概述

特别特别感谢这位童鞋His blog

舞蹈链是一种优美的搜索,就像下面这样跳舞~



舞蹈链用于解决精确覆盖或者重复覆盖的问题

你可以想象成贪吃蛇的一个上下左右联通的地图

\(Dancing Links\)就是通过链表这样实现的

网上有图的博客

二、实现

更详细的讲解在课件中

精确覆盖

精确覆盖大概指的就是数独和八皇后那样的问题

矩阵中选择一个行的集合,使得每列有且只有一个1

那么就是说每个格子上的点都有若干限制条件(行、列、对角线),每个条件都只允许一个元素

在舞蹈链中(可以把它看作一个表格),每个元素看作一行,限制条件转化为列,选一行删去也同时要删去这一行中所有点所在的列

然后舞蹈链兹瓷快速删除这些东西和快速回溯(复杂度未知)

大概有\(init\)、\(link\)、\(remove\)、\(resume\)、\(dance\)五个函数

实现的话看代码吧,有详细的注释

Code - [luogu1219]八皇后

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100100;
int ans,nn,o;
struct out{int a[14];}Ans[N];
namespace DLX
{
int n,m,cnt;//长宽,点的数量
int l[N],r[N],u[N],d[N];//上下左右的情况
int row[N],col[N];//每个点所处的行列
int h[N],s[N];//头节点和每列节点数
int ansk[20];//答案
void init(int nn,int mm)
{
//这个表格被循环套了起来,就像贪吃蛇的地图,左右和上下相通
//预先给第0行的每一列弄一个点
n=nn,m=mm;
for(int i=0;i<=m;i++)
r[i]=i+1,l[i]=i-1,u[i]=d[i]=i;
r[m]=0;l[0]=cnt=m;
memset(h,-1,sizeof(h));
}
void link(int R,int C)//在R行C列插入点
{
s[C]++;cnt++;//先记录这个点的各种信息
row[cnt]=R; col[cnt]=C;
//把列的链表改动
u[cnt]=C;
d[cnt]=d[C];
u[d[C]]=cnt;
d[C]=cnt;
//把行的链表改动
if(h[R]<0) h[R]=l[cnt]=r[cnt]=cnt;
else
{
r[cnt]=h[R];
l[cnt]=l[h[R]];
r[l[h[R]]]=cnt;
l[h[R]]=cnt;
}
}
void remove(int C)//删除C列以及C列上有点的行
{
r[l[C]]=r[C]; l[r[C]]=l[C];
for(int i=d[C];i!=C;i=d[i])
for(int j=r[i];j!=i;j=r[j])
{
u[d[j]]=u[j];
d[u[j]]=d[j];
s[col[j]]--;//是减得只剩下1吗(dei)
}
}
void resume(int C)//恢复C列以及C列上有点的行
{
r[l[C]]=C; l[r[C]]=C;
for(int i=d[C];i!=C;i=d[i])
for(int j=r[i];j!=i;j=r[j])
{
u[d[j]]=j;
d[u[j]]=j;
s[col[j]]++;
}
}
void dance(int deep)
{
int C=r[0];//找第一个限制条件
if(C>2*nn)//如果所有的行已经被删完就统计答案(能不能>2n)
{
ans++;
for(int i=0,x,y;i<deep;i++)
{
//记录下来选的点的编号,用编号还原行列
x=ansk[i]%nn;
y=(ansk[i]-1)/nn+1;
if(x==0) x=nn;
Ans[ans].a[y]=x;//x和y是等价的,可以交换
}
return;
}
for(int i=C;i<=nn;i=r[i])//找到点最少的列
/*
这是一处剪枝,因为删掉点最少的列,就是为了满足这个限制条件
需要枚举删掉的点就少一些,从而使得之后的剪枝更高效
相当于把搜索树繁茂的地方留给叶子,而深度越深越容易被剪枝
不加会T
*/
if(s[i]<s[C]) C=i;
remove(C);//删掉这一列
for(int i=d[C];i!=C;i=d[i])//枚举答案是这一列的哪个点,因为每一列只能选一个点,所以枚举选哪个
{
ansk[deep]=row[i];//记录答案,这个点编号是row[i]
for(int j=r[i];j!=i;j=r[j]) remove(col[j]);//这个点的行也得删了,把这行有点的列也删掉
dance(deep+1);
for(int j=r[i];j!=i;j=r[j]) resume(col[j]);//回溯
}
resume(C);//回溯过程
}
}
int cmp(const out&A,const out&B)
{
int p=0;while(A.a[p]==B.a[p]) p++;
return A.a[p]<B.a[p];
}
int main()
{
/// freopen("a.out","w",stdout);
scanf("%d",&nn);
/*
nn*nn个格子,每个格子看作舞蹈链的一行
总共有nn行nn列nn×2-1左对角nn×2-1右对角 共6×nn-2个限制
把每个限制看作一列,进行精准覆盖
*/
DLX::init(nn*nn,6*nn-2);
for(int i=1;i<=nn;i++)
for(int j=1;j<=nn;j++)
{
o++;
DLX::link(o,i);//占据第i行
DLX::link(o,j+nn);//占据第j列(能不能不写这一句)
DLX::link(o,i-j+3*nn);//占据第i-j+nn个左上到右下的对角线
DLX::link(o,i+j+4*nn-2);//占据第i+j-1个右上到左下的对角线
}
DLX::dance(0);//跳舞啦
sort(Ans+1,Ans+ans+1,cmp);
for(int i=1;i<=3;i++,puts(""))
for(int j=1;j<=nn;j++) printf("%d ",Ans[i].a[j]);
printf("%d\n",ans);return 0;
}

Code - Easy Finding戳我

重复覆盖

矩阵中选择一个行的集合,使得每列至少有一个1

所以选了一列之后不能把列中有1的所有的行删掉,复杂度会提高,加一个估价函数的\(A*\)剪枝

Code - [FZU1686]神龙的难题

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
const int N=70000;
int A[20][20],n,m,n1,m1;
int o,ans,tt;
namespace DLX
{
int n,m,p,u[N],d[N],l[N],r[N];
int col[N],row[N],h[300],s[300],vis[300];
void init(int nn,int mm)
{
n=nn,m=mm;
for(int i=0;i<=m;i++)
l[i]=i-1,r[i]=i+1,d[i]=u[i]=i,s[i]=0;
p=m;l[0]=m;r[m]=0;
memset(h,-1,sizeof(h));
}
void link(int R,int C)
{
p++;row[p]=R;col[p]=C;s[C]++;
d[p]=C;u[p]=u[C];
d[u[C]]=p;u[C]=p;
if(h[R]<0) h[R]=l[p]=r[p]=p;
else r[p]=h[R],l[p]=l[h[R]],r[l[h[R]]]=p,l[h[R]]=p;
}
void remove(int C)
{
for(int i=d[C];i!=C;i=d[i])
l[r[i]]=l[i],r[l[i]]=r[i];
}
void resume(int C)
{
for(int i=u[C];i!=C;i=u[i])
l[r[i]]=i,r[l[i]]=i;
}
int H()
{
int res=0;
memset(vis,0,sizeof(vis));
for(int i=r[0];i;i=r[i])
{
if(vis[i]) continue;
vis[i]=1; res++;
for(int j=d[i];j!=i;j=d[j])
for(int k=r[j];k!=j;k=r[k])
vis[col[k]]=1;
}
return res;
}
void dance(int step)
{
if(step+H()>=ans) return;
if(r[0]==0) {ans=min(ans,step);return;}
int C=r[0];
for(int i=r[C];i;i=r[i]) if(s[i]<s[C]) C=i;
for(int i=d[C];i!=C;i=d[i])
{
remove(i);
for(int j=r[i];j!=i;j=r[j]) remove(j);
dance(step+1);
for(int j=l[i];j!=i;j=l[j]) resume(j);
resume(i);
}
}
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
o=1;ans=1e9;tt=0;
for(int i=1;i<=n;i++)
for(int j=1,x;j<=m;j++)
{
scanf("%d",&x);
if(x) A[i][j]=++tt;
else A[i][j]=0;
}
scanf("%d%d",&n1,&m1);
DLX::init((n-n1+1)*(m-m1+1),tt);
for(int i=1;i<=n-n1+1;i++)
for(int j=1;j<=m-m1+1;j++,o++)
for(int x=i;x<=i+n1-1;x++)
for(int y=j;y<=j+m1-1;y++)
if(A[x][y]) DLX::link(o,A[x][y]);
DLX::dance(0);printf("%d\n",ans);
}
}

三、尾声

舞蹈链的复杂度是指数级别的,但是由于有非常强大的剪枝所以可以有玄学复杂度

在一般竞赛中舞蹈链并没有很广泛的应用和考察

但是这种思想需要大家了解,体会其中舞蹈的优美

弄个题单

舞蹈链(DLX)的更多相关文章

  1. 舞蹈链 DLX

    欢迎访问——该文出处-博客园-zhouzhendong 去博客园看该文章--传送门 舞蹈链是一个非常玄学的东西…… 问题模型 精确覆盖问题:在一个01矩阵中,是否可以选出一些行的集合,使得在这些行的集 ...

  2. [学习笔记] 舞蹈链(DLX)入门

    "在一个全集\(X\)中若干子集的集合为\(S\),精确覆盖(\(\boldsymbol{Exact~Cover}\))是指,\(S\)的子集\(S*\),满足\(X\)中的每一个元素在\( ...

  3. luogu P4929 【模板】舞蹈链 DLX

    LINK:舞蹈链 具体复杂度我也不知道 但是 搜索速度极快. 原因大概是因为 每次检索的时间少 有一定的剪枝. 花了2h大概了解了这个东西 吐槽一下题解根本看不懂 只能理解大概的想法 核心的链表不太懂 ...

  4. P4929-[模板]舞蹈链(DLX)

    正题 题目链接:https://www.luogu.com.cn/problem/P4929 题目大意 \(n*m\)的矩形有\(0/1\),要求选出若干行使得每一列有且仅有一个\(1\). 解题思路 ...

  5. Vijos1755 靶形数独 Sudoku NOIP2009 提高组 T4 舞蹈链 DLX

    欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目(传送门) 题意概括 给出一个残缺的数独,求这个数独中所有的解法中的最大价值. 一个数独解法的价值之和为每个位置所填的数值 ...

  6. POJ3076 Sudoku 舞蹈链 DLX

    欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目(传送门) 题意概括 给出一个残缺的16*16数独,求解. 题解 DLX + 矩阵构建  (两个传送门) 学完这个之后,再 ...

  7. POJ3074 Sudoku 舞蹈链 DLX

    欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目(传送门) 题意概括 给出一个残缺的数独,求解. 题解 DLX + 矩阵构建  (两个传送门) 代码 #include & ...

  8. POJ2676 Sudoku 舞蹈链 DLX

    欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目(传送门) 题意概括 给出一个残缺的数独,求解.SPJ 题解 DLX + 矩阵构建  (两个传送门) 代码 #includ ...

  9. 关于用舞蹈链DLX算法求解数独的解析

    欢迎访问——该文出处-博客园-zhouzhendong 去博客园看该文章--传送门 描述 在做DLX算法题中,经常会做到数独类型的题目,那么,如何求解数独类型的题目?其实,学了数独的构建方法,那么DL ...

  10. POJ3740 Easy Finding 舞蹈链 DLX

    欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目 精确覆盖问题模板题 算法 DLX算法 学习DLX算法--传送门 代码 #include <cstring> ...

随机推荐

  1. JSON数据的优化

    JSON数据的可以优化吗?肯定可以,之前都是使用XML,但之后用XML越来越少了,就是因为XML包含的冗余信息太多了.那么JSON数据有没有冗余呢? 肯定有,名称就是重复的!! 例如如下的JSON数据 ...

  2. js 时间戳和日期互转

    // 获取当前时间戳(以s为单位) var timestamp = Date.parse(new Date()); timestamp = timestamp / 1000; //当前时间戳为:140 ...

  3. JavaScript数组的一些奇葩行为

    今天,复习了一下JavaScript的数组,然后,把他的一些奇葩行为总结了一下,在这里和大家share一下,如果有不对的地方,欢迎指出! 奇葩1:Array()构造器函数可以不使用new关键字进行调用 ...

  4. 使用apache cxf实现webservice服务

    1.在idea中使用maven新建web工程并引入spring mvc,具体可以参考https://www.cnblogs.com/laoxia/p/9311442.html; 2.在工程POM文件中 ...

  5. scrapy 项目通过scrapyd部署

    年前的时候采用scrapy 爬取了某网站的数据,当时只是通过crawl 来运行了爬虫,现在还想通过持续的爬取数据所以需要把爬虫部署起来,查了下文档可以采用scrapyd来部署scrapy项目,scra ...

  6. Jenkins入门-部署gitlab 项目(8)

    目前很多公司代码管理已经迁入到了git上,大多数的公司使用的代码管理服务器是gitlab,目前持续交付的流行很多公司都采用Jenkins完成持续交付工作.首先我们需要通过Jenkins来获取我们的项目 ...

  7. MySQL 过滤数据(WHERE子句)

    WHERE子句操作符 从表products中检索prod_name,prod_price列,返回prod_price=2.5的行 mysql> SELECT  prod_name, prod_p ...

  8. asp.net:如何实现负载均衡方案讨论 (nginx+iis实现负载均衡)

    5台阿里云服务器,ip地址分别为 ip地址                      名字简称      操作系统       iis服务器     cpu   内存DDR3      机械硬盘 11 ...

  9. golang中defer的详解 转自https://blog.csdn.net/skh2015java/article/details/77081250

    Go里的defer很有用,尤其在很多执行模块化操作时,初始化时给各个需要执行的模块传入参数,但是这些参数有些事在模块执行过程中才赋值的. 这时候有了defer就不会把代码写的很凌乱. Go的defer ...

  10. C#发布程序添加其他程序文件

    注:程序发布文件,默认只发布自身程序直接引用的相关文件(A程序). 如果需要添加其他程序(不同的应用程序B)文件,操作方法如下: 第一步:将B程序文件复制到A程序 第二步:将B程序文件右键--> ...