名字:

\(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\)

\[A=\left(
\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)
\]

选择第一列:

\[\left(
\begin{matrix}
0&0&1&0&1&1&0\\\\\\\\\\
\end{matrix}
\right)
\]

将有\(1\)的列向下延伸,若该行有\(1\),标记该行,处理\(A\)矩阵,得到\(B\)矩阵

\[B=\left(
\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矩阵\)(删除所标记的行和列)

\[A_1=\left(
\begin{matrix}
1&0&1&1\\1&0&1&0\\0&1&0&1
\end{matrix}
\right)
\]

再选择第一列

\[\left(
\begin{matrix}
1&0&1&1\\\\\\
\end{matrix}
\right)
\]

标记

\[B_1=
\left(
\begin{matrix}
1&0&1&1\\1&&1&0\\0&1&0&1
\end{matrix}
\right)
\]

得到新矩阵,又得到一个规模较小的精确覆盖问题

\[S_1=
\left(
\begin{matrix}
0
\end{matrix}
\right)
\]

发现\(A\)矩阵不是空的,也没有一列有\(1\)(无法继续操作)

回溯

\[A_1=\left(
\begin{matrix}
1&0&1&1\\1&0&1&0\\0&1&0&1
\end{matrix}
\right)
\]

不能尝试第一行,标记第二行,尝试继续拓展

\[B_2=
\left(
\begin{matrix}
1&0&1&1\\1&0&1&0\\0&&0
\end{matrix}
\right)
\]

同理可得

\[A_3=
\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\),返回未解决,退出函数

例题 P4929

代码实现

#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)的更多相关文章

  1. 舞蹈链 DLX

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

  2. JS function 是函数也是对象, 浅谈原型链

    JS function 是函数也是对象, 浅谈原型链 JS 唯一支持的继承方式是通过原型链继承, 理解好原型链非常重要, 我记录下我的理解 1. 前言 new 出来的实例有 _proto_ 属性, 并 ...

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

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

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

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

  5. 浅谈树链剖分 F&Q

    这是一篇迟来的博客,由于我懒得写文章,本篇以两个问题阐述笔者对树链剖分的初步理解. Q1:树链剖分解决什么问题? 树链剖分,就是把一棵树剖分成若干连续的链,将这些链里的数据映射在线性数组上维护.比方说 ...

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

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

  7. 浅谈树链剖分(C++、算法、树结构)

    关于数链剖分我在网上看到的有几个比较好的讲解,本篇主要是对AC代码的注释(感谢各位witer的提供) 这是讲解 http://www.cnblogs.com/kuangbin/archive/2013 ...

  8. 蒟蒻浅谈树链剖分之一——两个dfs操作

    树链剖分,顾名思义就是将树形的结构剖分成链,我们以此便于在链上操作 首先我们需要明白在树链剖分中的一些概念 重儿子:某节点所有儿子中子树最多的儿子 重链:有重儿子构成的链 dfs序:按重儿子优先遍历时 ...

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

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

  10. POJ3076 Sudoku 舞蹈链 DLX

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

随机推荐

  1. oeasy教您玩转vim - 13 - # 大词小词

    大词小词 回忆上节课内容 我们上次学习了 e e 代表 end 词尾 自有跳跃 还可以成倍次数的跳跃 但其实我是想以一个一个属性地跳跃,有没有方法呢? 查询帮助 没思路的话我们还是得继续查询 :h w ...

  2. oeasy教您玩转vim - 31 - # 文字区块

    ​ 文字区块 回忆上节课内容 上上次讲的翻页 上次先让屏幕位置固定,移动光标 H- Head 移动到屏幕的顶端 M- Middle 移动到屏幕的中间 L- Low 移动到屏幕的底部 然后让光标固定,移 ...

  3. Volatile不保证原子性及解决方案

    原子性的意义 原子性特别是在并发编程领域,是一个极其重要的概念,原子性指的是一个操作或一组操作要么全部执行成功,要么全部不执行,不会出现部分执行的情况.这意味着原子性操作是不可分割的,它们在执行过程中 ...

  4. .NET Core 3.x 基于AspectCore实现AOP,实现事务、缓存拦截器

    最近想给我的框架加一种功能,就是比如给一个方法加一个事务的特性Attribute,那这个方法就会启用事务处理.给一个方法加一个缓存特性,那这个方法就会进行缓存.这个也是网上说的面向切面编程AOP. A ...

  5. C# Win10缩放导致Winform字体模糊的解决方法

    问题描述 现在的笔记本电脑分辨率很高,基本上能达到1920*1080以上,因为笔记本的屏幕小,在这样的分辨率下一切看着都很小,尤其是文字,根本看不清,所以Win10很人性化的提供了屏幕缩放功能,一般默 ...

  6. npm和yarn 命令比较

    命令比较 npm init | yarn init:创建一个新包 npm run | yarn run:运行 package.json 中定义的脚本 npm test | yarn test:测试一个 ...

  7. PHP转Go系列 | 推荐一个强大的Go语言工具函数库

    大家好,我是码农先森. 从 PHP 转到 Go 的朋友,常常会因为没有便捷的工具函数而感到苦恼.PHP 写的多了就会形成路径依赖,在写 Go 的时候时不时就会想到 PHP 强大的数组函数.当然写 Go ...

  8. 记hashmap

    hashmap是map接口的一个实现类,在同步的情况下hashmap的性能是比较好的 hashmap就是一个kv键值对的集合,将数值散列均匀的存储在哈希表中.插入方法为map.put(k,v),读取方 ...

  9. ORACLE PL/SQL 对象、表数据对比功能存储过程简单实现

    最近帮忙跟进个oracle11g upgrade 升级到19c 的项目,由于业主方不太熟悉oracle upgrade相关升级流程,以及升级影响范围相关的事项,担心应用停机升级以后会导致数据库保存的业 ...

  10. Jmeter函数助手31-changeCase

    changeCase函数用于字符转换大小写. 字符串修改:填入需要转换的字符 更改案例模式UPPER(默认),LOWER,CAPITALIZE:不填默认UPPER,UPPER全部转换为大写,LOWER ...