浅谈舞蹈链(DLX)
名字:
\(DL\),\(Dancing\space Link\),舞蹈链,是由\(Donald\space Knuth\)提出的数据结构,用来优化 \(X\) 算法,所以叫\(DLX\)
\(X\)算法详解
用于求解精确覆盖问题,精确覆盖问题的定义:给定一个由0-1组成的矩阵,是否能找到一个行的集合,使得集合中每一列都恰好包含一个1
例:
若\(A,B,C \subseteq S\)
\(A \space \cap \space B= Ø\),\(A \space \cup \space B=S\),则集合\(A,B\)是问题的一种解
\(X\)算法求解模拟:
给出矩阵\(A\)
\begin{matrix}
0&0&1&0&1&1&0\\1&0&0&1&0&0&1\\0&1&1&0&0&1&0\\1&0&0&1&0&0&0\\0&1&0&0&0&0&1\\0&0&0&1&1&0&1
\end{matrix}
\right)
\]
选择第一列:
\begin{matrix}
0&0&1&0&1&1&0\\\\\\\\\\
\end{matrix}
\right)
\]
将有\(1\)的列向下延伸,若该行有\(1\),标记该行,处理\(A\)矩阵,得到\(B\)矩阵
\begin{matrix}
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{matrix}
\right)
\]
\(S矩阵=A矩阵-B矩阵\)(删除所标记的行和列)
\begin{matrix}
1&0&1&1\\1&0&1&0\\0&1&0&1
\end{matrix}
\right)
\]
再选择第一列
\begin{matrix}
1&0&1&1\\\\\\
\end{matrix}
\right)
\]
标记
\left(
\begin{matrix}
1&0&1&1\\1&&1&0\\0&1&0&1
\end{matrix}
\right)
\]
得到新矩阵,又得到一个规模较小的精确覆盖问题
\left(
\begin{matrix}
0
\end{matrix}
\right)
\]
发现\(A\)矩阵不是空的,也没有一列有\(1\)(无法继续操作)
回溯
\begin{matrix}
1&0&1&1\\1&0&1&0\\0&1&0&1
\end{matrix}
\right)
\]
不能尝试第一行,标记第二行,尝试继续拓展
\left(
\begin{matrix}
1&0&1&1\\1&0&1&0\\0&&0
\end{matrix}
\right)
\]
同理可得
\left(
\begin{matrix}
1&1
\end{matrix}
\right)
\]
\(A_3\)中只有\(1\)行,且都是\(1\),选择这行,问题就可以解决
故,问题的解就是矩阵\(A\)中的第一行、矩阵\(A_1\)中的第\(2\)行、矩阵\(A_3\)的第一行,即矩阵\(A\)中的第\(1、4、5\)行
言归正传,DLX又是什么东西呢
介绍DL,舞蹈链

建出形如这样的交叉十字循环双向链,只对有\(1\)的连边,这张图对应着矩阵\(A\)

获取\(head.right\)元素,即元素\(C1\),标示元素\(C1\)为紫色

先尝试行\(2\),标示该行中其他元素\(元素5和元素6\)所在的列首元素为橙色,即元素\(C4\)和元素\(C7\)

移除橙色部分和紫色部分,剩下的如图所示

获取\(head.right\)元素,即元素\(C2\),标示元素\(C2\)为紫色

列\(C2\)只有元素\(7\)覆盖,故答案只能选择行\(3\)
选择行\(3\),同理,标示元素\(C3\)和标示元素\(C6\)为橙色

移除,得

没有元素能覆盖到\(C5\),说明求解失败,回溯
回标列首元素,其顺序是标示元素的顺序反过来,即回标列首C6、回标列首C3、回标列首C2、回标列首C7、回标列首C4

故不能选择行\(2\),尝试行\(4\),同理,标示元素\(C4\)为橙色

移除,得

获取\(head.right\)元素,标示元素\(C2\)为紫色

选择行\(3\),同理,标示元素\(C3\)和\(C6\)为橙色

同理,得

同理,回溯,回标列首C6、回标列首C3

这次选择行\(5\),标示元素\(C7\)为橙色

移除,得

获取\(head.right\)元素,标示元素\(C3\)为紫色

只有元素\(1\)覆盖,故答案只能选择行\(3\),同理,标示元素\(C5\)和元素\(C6\)为橙色

移除,得

因为\(head.right=head\),故求解结束,答案栈中的答案分别是\(4、5、1\),表示该问题的解是第\(4、5、1\)行覆盖所有的列,即下图蓝色的部分

总结\(Dancing Links\)的求解
- 函数入口
- 判断$head.right \space ?= head $,若成立,输出答案,返回已解决,退出函数
- 获得\(head.right\)的元素\(C\)
- 标示元素\(C\)
- 获得元素\(C\)所在列的一个元素
- 标示该元素同行的其他元素所在的列首元素
- 获得一个简化问题,递归,若返回已解决,则退出函数
- 若刚刚尝试的不行,回标该元素同行的其他元素所在的列首元素,回标顺序与之前标示的顺序相反
- 获得元素\(C\)所在列的下一个元素,若有,跳转“标示该元素同行的其他元素所在的列首元素”
- 若没有,回标元素\(C\),返回未解决,退出函数
代码实现
#include"bits/stdc++.h"
using namespace std;
const int N=250015;//N*M
#define inl inline
#define reg register
#define regi register int
#define PII pair<int,int>
inl int read(void)
{
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*f;
}
int l[N],r[N],up[N],down[N],col[N],row[N];//每个点的左右上下指针,以及每个点所在的行列
int head[N];int sz[N];//每行的头结点,每列的节点数
int ans[N];//答案栈
int n,m,cnt;
void build(int m)
{
for(int i=0;i<=m;i++) r[i]=i+1,l[i]=i-1,up[i]=down[i]=i;
r[m]=0,l[0]=m;
memset(sz,0,sizeof sz);
cnt=m+1;//已经处理了0行,从第一行开始insert
}
void insert(int R,int C)//在R行C列插入点
{
sz[C]++;
row[++cnt]=R,col[cnt]=C;
up[cnt]=C,down[cnt]=down[C];
up[down[C]]=cnt,down[C]=cnt;
if(!head[R]) head[R]=r[cnt]=l[cnt]=cnt;
else r[cnt]=head[R],l[cnt]=l[head[R]],r[l[head[R]]]=cnt,l[head[R]]=cnt;
}
void remove(int C)//删除C列的集合
{
r[l[C]]=r[C],l[r[C]]=l[C];
for(int i=down[C];i!=C;i=down[i])
for(int j=r[i];j!=i;j=r[j])
up[down[j]]=up[j],down[up[j]]=down[j],sz[col[j]]--;
}
void resume(int C)//恢复C列的集合
{
for(int i=up[C];i!=C;i=up[i])
for(int j=l[i];j!=i;j=l[j])
up[down[j]]=down[up[j]]=j,sz[col[j]]++;
r[l[C]]=C,l[r[C]]=C;
}
bool dance(int dep)
{
if(r[0]==0)//head.right=head
{
for(int i=0;i<dep;i++) printf("%d ",ans[i]);
return 1;
}
int C=r[0];
for(int i=r[0];i;i=r[i]) if(sz[i]<sz[C]) C=i;//找到点最少的列(优化)
remove(C);
for(int i=down[C];i!=C;i=down[i])
{
ans[dep]=row[i];//压入答案栈
for(int j=r[i];j!=i;j=r[j]) remove(col[j]);
if(dance(dep+1)) return 1;
for(int j=l[i];j!=i;j=l[j]) resume(col[j]);
}
resume(C);
return 0;
}
int main(void)
{
n=read(),m=read();
int s;
build(m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
s=read();
if(s) insert(i,j);
}
if(!dance(0)) puts("No Solution!");//无解
return 0;
}
浅谈舞蹈链(DLX)的更多相关文章
- 舞蹈链 DLX
欢迎访问——该文出处-博客园-zhouzhendong 去博客园看该文章--传送门 舞蹈链是一个非常玄学的东西…… 问题模型 精确覆盖问题:在一个01矩阵中,是否可以选出一些行的集合,使得在这些行的集 ...
- JS function 是函数也是对象, 浅谈原型链
JS function 是函数也是对象, 浅谈原型链 JS 唯一支持的继承方式是通过原型链继承, 理解好原型链非常重要, 我记录下我的理解 1. 前言 new 出来的实例有 _proto_ 属性, 并 ...
- [学习笔记] 舞蹈链(DLX)入门
"在一个全集\(X\)中若干子集的集合为\(S\),精确覆盖(\(\boldsymbol{Exact~Cover}\))是指,\(S\)的子集\(S*\),满足\(X\)中的每一个元素在\( ...
- luogu P4929 【模板】舞蹈链 DLX
LINK:舞蹈链 具体复杂度我也不知道 但是 搜索速度极快. 原因大概是因为 每次检索的时间少 有一定的剪枝. 花了2h大概了解了这个东西 吐槽一下题解根本看不懂 只能理解大概的想法 核心的链表不太懂 ...
- 浅谈树链剖分 F&Q
这是一篇迟来的博客,由于我懒得写文章,本篇以两个问题阐述笔者对树链剖分的初步理解. Q1:树链剖分解决什么问题? 树链剖分,就是把一棵树剖分成若干连续的链,将这些链里的数据映射在线性数组上维护.比方说 ...
- P4929-[模板]舞蹈链(DLX)
正题 题目链接:https://www.luogu.com.cn/problem/P4929 题目大意 \(n*m\)的矩形有\(0/1\),要求选出若干行使得每一列有且仅有一个\(1\). 解题思路 ...
- 浅谈树链剖分(C++、算法、树结构)
关于数链剖分我在网上看到的有几个比较好的讲解,本篇主要是对AC代码的注释(感谢各位witer的提供) 这是讲解 http://www.cnblogs.com/kuangbin/archive/2013 ...
- 蒟蒻浅谈树链剖分之一——两个dfs操作
树链剖分,顾名思义就是将树形的结构剖分成链,我们以此便于在链上操作 首先我们需要明白在树链剖分中的一些概念 重儿子:某节点所有儿子中子树最多的儿子 重链:有重儿子构成的链 dfs序:按重儿子优先遍历时 ...
- Vijos1755 靶形数独 Sudoku NOIP2009 提高组 T4 舞蹈链 DLX
欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目(传送门) 题意概括 给出一个残缺的数独,求这个数独中所有的解法中的最大价值. 一个数独解法的价值之和为每个位置所填的数值 ...
- POJ3076 Sudoku 舞蹈链 DLX
欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目(传送门) 题意概括 给出一个残缺的16*16数独,求解. 题解 DLX + 矩阵构建 (两个传送门) 学完这个之后,再 ...
随机推荐
- API引用在Element UI (Vue 2)和Element Plus (Vue 3)中的不同
API 变动 样式类名变化: 一些组件的样式类名有所变动,可能需要更新你的自定义样式. 事件名和属性名变化: 某些组件的事件名和属性名发生了变化,需要检查 Element Plus 文档 以了解详细信 ...
- [oeasy]python0008_输出h字符_REPL_引号_括号_什么是函数
输出h字符_REPL_引号_括号_什么是函数 回忆上次内容 上次 继续在游乐场里 玩耍 键盘按键 作用 ↑ 上一条指令 ↓ 下一条指令 ← 光标 向左移动 一格 → 光标 向右移动 一格 ctrl + ...
- [MAUI 项目实战] 笔记App:程序设计
前言 有人说现在记事类app这么多,市场这么卷,为什么还想做一个笔记类App? 一来,去年小孩刚出生,需要一个可以记录喂奶时间的app,发现市面上没有一款app能够在两步内简单记录一个时间,可能iOS ...
- EFCore DbFirst从数据库生成实体类
1.点击"工具"->"NuGet包管理器"->"程序包管理器控制台" 分别安装以下几个包 Mysql 版本: Install-P ...
- 靶机: EvilBox---One
靶机: EvilBox---One 准备工作 靶机地址: https://download.vulnhub.com/evilbox/EvilBox---One.ova MD5 校验:c3a65197b ...
- 学习笔记--Java合集
学习笔记--Java合集 JDK8 基础篇 我的第一个Java程序 Java标识符 Java 字面值 Java中的变量 Java中的数据类型 Java 运算符 Java 控制语句 方法 Java方法基 ...
- SpringTask
SpringTask是spring提供的一个任务调度工具,按照约定的时间自动执行代码逻辑 定时任务框架,即定时自动执行某段代码 应用场景:信用卡每月还款提醒,火车售票系统处理未支付订单 cron表达式 ...
- python lambda 三元表达式
python lambda 三元表达式 python中的lambda函数用法 通常定义的函数 def sum(x,y): return x+y print(sum(4,6)) 用lambda来实现: ...
- 强化学习中子进程调用atari游戏是否受父进程中设置的随机种子影响
相关: python中numpy.random.seed设置随机种子是否影响子进程 ============================================ 代码: from ale_ ...
- WhaleStudio 2.6重磅发布!调度模块WhaleScheduler更新78项核心功能
我们很高兴地宣布WhaleStudio 2.6版本的正式发布!新版本中包含了数据调度模块WhaleScheduler和数据集成模块WhaleTunnel的百余项核心功能更新,本文摘选了WhaleSch ...