博客园同步

前置知识:

  • 一维链表。(单向,双向,循环)
  • 部分集合运算,如 ⋂ \bigcap ⋂, ⋃ \bigcup ⋃.

前言

在计算机科学中,X算法可用来求解精确覆盖问题。

精确覆盖问题 是哪一类问题呢? X X X 算法又是什么高深的算法呢?

背景

  • 你的同学通过某种算法迅速 AC \text{AC} AC 了 P1784 数独,然后他兴致勃勃地 带领学生 1 s 1s 1s 搞定数独竞赛

小时候,你玩数独;长大了,数独玩你。你该怎么办?

  • 那位同学用 DLX \text{DLX} DLX 轻松码力全开搞定了 P4205 『NOI2005』智慧珠游戏

小时候,你玩智慧珠;长大了,智慧珠玩你。你该怎么办?

定义

Dancing Links X \text{Dancing Links X} Dancing Links X 是优化后的 X X X 算法,用于解决 精确覆盖问题

精确覆盖问题 是这样一类问题:给定若干集合 S 1 , S 2 ⋯ S n S_1 , S_2 \cdots S_n S1​,S2​⋯Sn​ 和一个集合 X X X,求出 T 1 , T 2 ⋯ T k T1 , T2 \cdots T_k T1,T2⋯Tk​ 使得满足:

T i ⋂ T j = ∅ ( i ≠ j ) T_i \bigcap T_j = \varnothing (i \not = j) Ti​⋂Tj​=∅(i​=j)

⋃ i = 1 k T i = X \bigcup_{i=1}^k T_i = X i=1⋃k​Ti​=X

∀ i ∈ [ 1 , k ] , T i ∈ S 1 , S 2 ⋯ S n ∀ i \in [1,k] , T_i \in {S_1 , S_2 \cdots S_n} ∀i∈[1,k],Ti​∈S1​,S2​⋯Sn​

翻译成人话 其实就是在 n n n 个集合中选出若干 两两不相交 的集合且 这些集合的并为 X X X.

比方说:

S 1 = 666 , 2 , 4 S_1 = {666,2,4} S1​=666,2,4

S 2 = 2 , 4 , 8 , 119 S_2={2,4,8,119} S2​=2,4,8,119

S 3 = 8 , 5 S_3={8,5} S3​=8,5

S 4 = 666 , 2 , 4 , 119 S_4={666,2,4,119} S4​=666,2,4,119

S 5 = 666 , 4 , 5 S_5={666,4,5} S5​=666,4,5

X = 666 , 2 , 8 , 4 , 5 , 119 X={666,2,8,4,5,119} X=666,2,8,4,5,119

那么这时答案就应该是取 S 4 S_4 S4​ 和 S 3 S_3 S3​.

那么,如何解决这类问题呢?

算法一

暴力搜索解决问题。

我们用 2 n 2^n 2n 的时间枚举每个集合是否被选择。然后 O ( n ) O(n) O(n) 验证即可。

时间复杂度: O ( 2 n × n m ) O(2^n \times nm) O(2n×nm).( m m m 为 ⋃ i = 1 n S i \bigcup_{i=1}^n S_i ⋃i=1n​Si​ 的大小)

算法二

不得不说算法一时间无法承受。

我们首先将 S S S 与 X X X 离散化,并构造矩阵 a ( n × m ) a (n \times m) a(n×m) ,其中 a i , j a_{i,j} ai,j​ 表示 S i S_i Si​ 是否含有离散化后的 j j j; m m m 为 ⋃ i = 1 n S i \bigcup_{i=1}^n S_i ⋃i=1n​Si​ 的大小。

那么,对上述问题建矩阵可得:

{ 1 1 0 1 0 0 0 1 1 0 1 1 0 0 1 0 1 0 1 1 0 1 0 1 1 0 0 1 1 0 } \begin{Bmatrix} 1 & 1 & 0 & 1 & 0 & 0 \\ 0 & 1 & 1 & 0 & 1 & 1 \\ 0 & 0 & 1 & 0 & 1 & 0 \\ 1 & 1 & 0 & 1 & 0 & 1 \\ 1 & 0 & 0 & 1 & 1 & 0 \\ \end{Bmatrix} ⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧​10011​11010​01100​10011​01101​01010​⎭⎪⎪⎪⎪⎬⎪⎪⎪⎪⎫​

其中 1 1 1 ~ 6 6 6 列分别表示: ∈ 666 , ∈ 2 , ∈ 8 , ∈ 4 , ∈ 5 , ∈ 119 \in 666 , \in 2 , \in 8 , \in 4 , \in 5 , \in 119 ∈666,∈2,∈8,∈4,∈5,∈119.

问题转化为:求 01 01 01 矩阵选出若干行,使得每列 有且只有 1 1 1 个 1 1 1.

考虑将每行看做一个二进制数,即要求所有选出行的 为 2 m − 1 2^m-1 2m−1,且任意两个数的 为 0 0 0.

用一个变量记录当前或值,然后计算即可。

时间复杂度: O ( 2 n × n ) O(2^n \times n) O(2n×n).

算法三

考虑 X X X 算法。回顾一下这个图:

{ 1 1 0 1 0 0 0 1 1 0 1 1 0 0 1 0 1 0 1 1 0 1 0 1 1 0 0 1 1 0 } \begin{Bmatrix} 1 & 1 & 0 & 1 & 0 & 0 \\ 0 & 1 & 1 & 0 & 1 & 1 \\ 0 & 0 & 1 & 0 & 1 & 0 \\ 1 & 1 & 0 & 1 & 0 & 1 \\ 1 & 0 & 0 & 1 & 1 & 0 \\ \end{Bmatrix} ⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧​10011​11010​01100​10011​01101​01010​⎭⎪⎪⎪⎪⎬⎪⎪⎪⎪⎫​

首先我们选择第一行,然后把第一行的 1 1 1 所在列标记,把所有 被标记的列上有 1 1 1 的那一行删除。 得到:

绿色表示标记的列,红色表示当前行,紫色表示被删除的行。

为什么呢?因为 当前列有了 1 1 1,别的列不能再有了;有的肯定不能选,删除它们是一个有力的剪枝。

然后你发现你只能选第 3 3 3 行,验证后发现第 6 6 6 列没有 1 1 1,所以回溯,不选第 1 1 1 行。回溯之前,需要 恢复之前删掉的行

注:图上 a 2 , 2 a_{2,2} a2,2​ 忘记标了,不过第 1 1 1 行不在我们考虑的范围内(已经确定不能选了),不会影响结果;但忘记标了可能会影响理解,大家谅解一下。

当然,这里如果为了方便理解把第一行删去也是可以的。

然后同理,你发现第 2 2 2 行选了之后无行可选。(因为前面的搜索已经确定了第 1 1 1 行选了无解,所以不选)

验证,发现第 1 1 1 列没有选 1 1 1 (后面的不用再看,一列无解就是无解了),直接回溯,说明第 2 2 2 行也不能选,恢复删除行。

然后选第 3 3 3 行。

咦?你发现可以递归下去选第 4 4 4 行。

验证发现正确!

所以得到答案: S 3 , S 4 S_3 , S_4 S3​,S4​,退出搜索。

所以, X X X 算法的步骤大致为:

  1. 对当前矩阵删除当前行,并删去与当前行有公共元素(即同列有 1 1 1) 的所有行,递归下一层。

  2. 如果所有行返回无解即返回无解给上一层,并恢复行,回到第 1 1 1 步。

  3. 如果发现所有列都被标记则输出答案,结束搜索。

  4. 所有搜索无解则返回无解。

你发现,该搜索需要大量的 删除行恢复行 的操作。

而 X X X 算法的时间复杂度是 指数级 的,其回溯、搜索层数取决于 矩阵中 1 1 1 的个数 而不是 n n n 或者 m m m 的大小。其时间复杂度大概可以接受 n , m ≤ 100 n,m \leq 100 n,m≤100 的情况。

算法四

Dancing Links 意为:跳舞的链 \text{Dancing Links 意为:跳舞的链} Dancing Links 意为:跳舞的链

为什么叫做 跳舞的链 呢?是这样的。

假设我们要写这样一道题目:

给定一个 n × m n \times m n×m 的二维矩阵,你需要支持 q q q 个操作:

  1. 删除 第 x x x 行 ;
  2. 查询 a i , j a_{i,j} ai,j​ 的值。
    数据范围: n , m ≤ 500 n,m \leq 500 n,m≤500, q ≤ 1 0 5 q \leq 10^5 q≤105.

当然如果你暴力删除的话,时间复杂度是 O ( n ⋅ m ⋅ q ) O(n \cdot m \cdot q) O(n⋅m⋅q).

但是我们想一个弱化版:

给定一个长为 n n n 的数组,你需要支持 q q q 个操作:

  1. 删除第 x x x 个数;
  2. 查询第 a i a_i ai​ 的值。
    数据范围: n ≤ 500 n \leq 500 n≤500, q ≤ 1 0 5 q \leq 10^5 q≤105.

这直接用一维链表弄一下就可以了是不?

所以,一个叫 Donald E. Knuth \texttt{Donald E. Knuth} Donald E. Knuth 的计算机科学家,发明了 “十字链表” 解决此类问题。

十字链表的图大概是这样的:

(下面十字链表的图,代码思路摘自 DLX 详细讲解)

似乎非常简单的样子,那再给一张:

说白了你第一眼看到我也是这个感觉:

别扯了,说正题。回到这个图:

每个节点弄个指针指向它上下左右的节点,然后再开数组记录每行,每列的元素个数。 f i r i fir_i firi​ 是我们在每行链表之前虚构的一个元素,每次从它开始遍历。

STEP 1

#define FOR(i,A,x) for(int i=A[x];i!=x;i=A[i])

预处理优化宏定义都好用

定义数组。

int n,m,id,fir[N],siz[N];
int L[N],R[N],U[N],D[N];
int col[N],row[N],ans;
int stk[N]; //stk 记录答案

STEP 2

如何建立一个 n × m n \times m n×m 的 Dancing Link? \text{Dancing Link?} Dancing Link? 很显然,对于一行是这样的:

inline void build(int x,int y) {//  printf("build : %d %d\n",x,y);n=x,m=y; for(int i=0;i<=y;i++)L[i]=i-1,R[i]=i+1,U[i]=D[i]=i; //左右是 i-1,i+1 , 上下就是自己L[0]=y,R[y]=0,id=y;memset(fir,0,sizeof(fir));memset(siz,0,sizeof(siz));
}

那你会说,嗯,不对呀?这样我们只是初始化一行而已?

对,所以我们对每个集合插入节点,通过插入来实现。

如果 idx \text{idx} idx 的位置已经有了,那么把它插入到已有的下面去,这样 同列的 1 1 1 就会被放在不同行 了;否则已经有的话就直接按照链表思路插入。插入顺序要记清!

inline void insert(int x,int y) {//  printf("insert : %d %d\n",x,y);col[++id]=y,row[id]=x,++siz[y]; //记录个数,插入
//  U[id]=D[id]=y,U[D[y]]=id,D[y]=id;D[id]=D[y],U[D[y]]=id,U[id]=y,D[y]=id; //维护上下if(!fir[x]) fir[x]=L[id]=R[id]=id; //没出现则自己作为第一个else R[id]=R[fir[x]],L[R[fir[x]]]=id,L[id]=fir[x],R[fir[x]]=id; //接在第一个后面
}

早警告过你今天前置知识是链表,看你不会链表一个也写不了了

STEP 3

那么如何删除一行呢?

嗯,上下互相指,然后每列个数减。是不是很简单?

inline void remove(int x) {//  printf("remove : %d\n",x);L[R[x]]=L[x]; R[L[x]]=R[x];FOR(i,D,x) FOR(j,R,i) U[D[j]]=U[j],D[U[j]]=D[j],--siz[col[j]];
}

那么同理考虑删除列:

inline void recover(int x) {//  printf("recover : %d\n",x);FOR(i,U,x) FOR(j,L,i) U[D[j]]=D[U[j]]=j,++siz[col[j]];L[R[x]]=R[L[x]]=x;
}

算法五

回忆一下 X X X 算法的过程:

  1. 对当前矩阵删除当前行,并删去与当前行有公共元素(即同列有 1 1 1) 的所有行,递归下一层。

  2. 如果所有行返回无解即返回无解给上一层,并恢复行,回到第 1 1 1 步。

  3. 如果发现所有列都被标记则输出答案,结束搜索。

  4. 所有搜索无解则返回无解。

加个剪枝: 选择列元素最少的删除,这个剪枝太显然了吧。因为元素少的更容易被决定。

你发现 删除行,列 可以直接用上面的十字链表维护。

我们要开始写最重要的 dance \text{dance} dance 啦! 其实十字链表真的很想跳舞的,不然不会叫这么名字的。

强烈建议读者返回亲自推一下那个图,不然代码实在理解不了。

inline bool dance(int dep) {//  printf("dance : %d\n",dep);if(!R[0]) {ans=dep;return 1;} //0 号没有右边元素 , 即整列都被标记,说明有答案int wz=R[0]; FOR(i,R,0) if(siz[i]<siz[wz]) wz=i; // 找到最小的那个remove(wz); FOR(i,D,wz) { //删除stk[dep]=row[i]; FOR(j,R,i) remove(col[j]); //标记的全部删除if(dance(dep+1)) return 1; //往下走FOR(j,L,i) recover(col[j]); //恢复} recover(wz); return 0; //恢复 , 返回无解
}

嗯,好了,大家只要弄明白每部分的意思,我们就来看题吧!

P4929 【模板】舞蹈链(DLX)

时间复杂度: O ( c n ) O(c^n) O(cn). 是指数级的, n n n 是矩阵 1 1 1 的个数, c c c 是某个很接近 1 1 1 ( > 1 >1 >1) 的常数。但一般而言不会出现卡 Dancing Links X \text{Dancing Links X} Dancing Links X 的出题人吧。

实际得分: 100 p t s 100pts 100pts.

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;const int N=1e5+1;
#define FOR(i,A,x) for(int i=A[x];i!=x;i=A[i])inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}int n,m,id,fir[N],siz[N];
int L[N],R[N],U[N],D[N];
int col[N],row[N],ans;
int stk[N];inline void remove(int x) {//  printf("remove : %d\n",x);L[R[x]]=L[x]; R[L[x]]=R[x];FOR(i,D,x) FOR(j,R,i) U[D[j]]=U[j],D[U[j]]=D[j],--siz[col[j]];
}inline void recover(int x) {//  printf("recover : %d\n",x);FOR(i,U,x) FOR(j,L,i) U[D[j]]=D[U[j]]=j,++siz[col[j]];L[R[x]]=R[L[x]]=x;
}inline void build(int x,int y) {//  printf("build : %d %d\n",x,y);n=x,m=y; for(int i=0;i<=y;i++)L[i]=i-1,R[i]=i+1,U[i]=D[i]=i;L[0]=y,R[y]=0,id=y;memset(fir,0,sizeof(fir));memset(siz,0,sizeof(siz));
}inline void insert(int x,int y) {//  printf("insert : %d %d\n",x,y);col[++id]=y,row[id]=x,++siz[y];
//  U[id]=D[id]=y,U[D[y]]=id,D[y]=id;D[id]=D[y],U[D[y]]=id,U[id]=y,D[y]=id;if(!fir[x]) fir[x]=L[id]=R[id]=id;else R[id]=R[fir[x]],L[R[fir[x]]]=id,L[id]=fir[x],R[fir[x]]=id;
}inline bool dance(int dep) {//  printf("dance : %d\n",dep);if(!R[0]) {ans=dep;return 1;}int wz=R[0]; FOR(i,R,0) if(siz[i]<siz[wz]) wz=i;remove(wz); FOR(i,D,wz) {stk[dep]=row[i]; FOR(j,R,i) remove(col[j]);if(dance(dep+1)) return 1;FOR(j,L,i) recover(col[j]);} recover(wz); return 0;
}int main(){n=read(),m=read(); build(n,m);for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(read()) insert(i,j);dance(1);if(ans) for(int i=1;i<ans;i++) printf("%d ",stk[i]);else puts("No Solution!");  return 0;
}
  • P1784 数独

利用精确覆盖解决问题!

分支题解

Link 代码

有了 DLX 解决精确覆盖,我们 1s AK 数独不是梦想!

浅谈 Dancing Links X 算法相关推荐

  1. 浅入 dancing links x(舞蹈链算法)

    abastract:利用dancing links 解决精确覆盖问题,例如数独,n皇后问题:以及重复覆盖问题. 要学习dacning links 算法,首先要先了解该算法适用的问题,精确覆盖问题和重复 ...

  2. 【转载】浅入 dancing links x(舞蹈链算法)

    转载自原文出处 浅入 dancing links x(舞蹈链算法) abastract:利用dancing links 解决精确覆盖问题,例如数独,n皇后问题:以及重复覆盖问题. 要学习dacning ...

  3. 2-路插入排序c语言算法,浅谈2路插入排序算法及其简单实现

    2路插入排序算法是在直接插入排序算法的基础上增加了一个辅助数组,其目的是减少排序过程中的移动次数,需要增加n个记录的辅助空间. 难点可能在于对取余的考虑吧,可以把辅助数组看成一个环状空间,这样就能更好 ...

  4. python算法程序_浅谈python常用程序算法

    一.冒泡排序: 1.冒泡排序是将无序的数字排列成从小到大的有序组合: 过程:对相邻的两个元素进行比较,对不符合要求的数据进行交换,最后达到数据有序的过程. 规律: 1.冒泡排序的趟数时固定的:n-1 ...

  5. 技术分享:浅谈滴滴派单算法

    浅谈滴滴派单算法 原创: 王犇 刘春阳 徐哲 滴滴技术 桔妹导读:说到滴滴的派单算法,大家可能感觉到既神秘又好奇,从出租车扬召到司机在滴滴平台抢单最后到平台派单,大家今天的出行体验已经发生了翻天覆地的 ...

  6. **浅谈三角测距激光雷达测距算法**

    浅谈三角测距激光雷达测距算法 此篇文章主要对我这半年以来对激光雷达光斑定位算法上一些粗浅的见解,三角测距的基本原理在这里就不做基本的叙述,百度一下都有.本文主要对三角法激光雷达比较重要和比较难以匹配的 ...

  7. 浅谈网络流的基本算法

    引言 过去听起来高深莫测的网络流算法,现在已飞入寻常百姓家了,对于每一个OIER,网络流是一个神圣的东西(个人见解),但神圣的同时,它并不是那样抽象,最形象的模型就是水流,从长江源点无限的向外流水,而 ...

  8. 浅谈单图像三维重建算法

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 作者丨lcl 来源丨 我爱计算机视觉 本文简单带大家看一下单图像三维重建相关的论文.写这篇文章算是对之 ...

  9. 浅谈:斐波那契搜索算法(Fibonacci search)

    一:有趣的背景 ​ 谈到斐波那契查找算法,总是有一个神奇的数字与之紧密相连--黄金分割数(0.618).黄金分割数被公认为最具有审美意义的比例数字,这个数值的作用不仅仅体现在诸如绘画.雕塑.音乐.建筑 ...

最新文章

  1. access导出MySQL表格_如何将Access数据库里的表内容导出到Excel
  2. hibernate fetch使用
  3. 【 FPGA 】FIR滤波器的采样速率与系统时钟速率不同时的资源消耗分析
  4. Ubuntu16.04连接SSH出现 Server responded “Algorithm negotiation failed” 的解决方法
  5. 计算机应用基础 专2018秋,广东开放大学远程教育专科2018年秋计算机应用基础Word模块测试.pdf...
  6. sqlserver安装目录_Jira试用报告(安装及数据迁移篇)
  7. 疫情反复,电赛会取消吗?
  8. HDMI转MIPI DSI芯片方案TC358779XBG
  9. 通过Cisco packet Tracer 划分vlan实现不同交换机间pc进行通信
  10. 广州专科计算机学校录取分数线,广州大专多少分能录取?高考分数170分能上广州大专?...
  11. 【vue项目使用echarts实现区域地图绘制,且可点击单独区域】
  12. flutter 仿微信朋友圈发布图片
  13. 计算机vb输入框函数,VB基本函数大全
  14. android计算器如何保存记录,计算器历史记录怎么看
  15. Codeforces Round #612 (Div. 1) A. Garland(dp动态规划)
  16. 2021-03-25
  17. MATLABSTM32CubeMX联合开发系列——不用手写一行代码就能实现CAN通讯
  18. 双屏幕 php,电脑双屏互不干扰
  19. 哈希表_四数之和(待完善)
  20. 图书系统(轻松上手)

热门文章

  1. C语言编程>第二十五周 ③ 下列给定程序中,函数fun的功能是:根据输入的三个边长(整型值),判断能否构成三角形;构成的是等边三角形,还是等腰三角形。若能构成等边三角形函数返回3,若能构成……
  2. 内网外网同时使用——双网关设置
  3. 达观OCR文字识别赋能公积金中心实现业务办理再提速
  4. Hibernate之Criteria查询及注解
  5. 【闪电侠学netty】第4章 服务端启动流程
  6. 用企业微信做好三级客户留存,客户流失率降低60%
  7. UniApp基础:简单入门
  8. 无忌上一位放弃佳能转投宾得的网友的文章
  9. 深度学习图像处理(深度学习的医学图像分割3.1)
  10. 速龙631电脑装机 硬件 参数 BIOS参数调整