CoinciDANCE——DLX学习笔记


为了优雅地解决数独问题,我找到了优雅的Dancing Links。

int n,m,cnt;//矩阵的长,宽,点的数量
    int l[mx],r[mx],u[mx],d[mx],row[mx],col[mx];//每个点的左,右,上,下,行,列信息
//△
    int h[mx];//每行的头结点
    int s[mx];//每列的结点数
    int ans,ansk[mx];//需要ans个子集,分别为ansk[]
  1. 在R行C列插入点
inline void link(int R,int C) {
		s[C]++;//第C列总的节点数+1
		row[cnt]=R;//该节点在第R行
		col[cnt]=C;//该节点在第C列
		u[cnt]=C;//它的上面节点编号是C
		d[cnt]=d[C];//它的下面节点编号是d[C]
		u[d[C]]=cnt;//d[C]的上面是当前节点cnt
		d[C]=cnt;//C的下面是cnt
//相当于在C列最上面列首哨兵直接下面插入一个节点cnt
//上为十字链表原理
		if(h[R]<0) h[R]=r[cnt]=l[cnt]=cnt;//该行没有别的点,把第一个加入的点作为该行的行头结点
		else {
			r[cnt]=h[R];
			l[cnt]=l[h[R]];
			r[l[h[R]]]=cnt;
			l[h[R]]=cnt;
		}
		cnt++;
	}

My模板code

洛谷P4929 【模板】舞蹈链(DLX)
AC 47ms

#include
using namespace std;
const int N=5510;
int n,m,a;
int u[N],d[N],l[N],r[N],row[N],col[N],cnt;
int sv[N],hd[N],ans[N],dep;

inline int read() {
	register int x=0,f=0;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-') f=1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f? -x : x;
}

inline void rec(int c) {
	register int i,j;
	for(i=u[c]; i!=c; i=u[i]) {
		for(j=l[i]; j!=i; j=l[j]) {
			u[d[j]]=d[u[j]]=j;
			sv[col[j]]++;
		}
	}
	r[l[c]]=l[r[c]]=c;
}

inline void rem(int c) {
	l[r[c]]=l[c];
	r[l[c]]=r[c];
	register int i,j;
	for(i=d[c]; i!=c; i=d[i]) {
		for(j=r[i]; j!=i; j=r[j]) {
			u[d[j]]=u[j];
			d[u[j]]=d[j];
			sv[col[j]]--;
		}
	}
}

bool dance(int tmp) {
	if(!r[0]) {
		dep=tmp;
		return true;
	}
	register int c=r[0],i,j;
	for(i=r[0]; i!=0; i=r[i]) {
		if(sv[i]

在写代码时有如下错误

  1. dance操作:对Dancing Links理解不够深刻。
    一共要进行两次remove操作:
    第一次remove当前矩阵中含1个数最少的列,以及这些1所在的行;第二次remove这些1所在的行上的其它1所在的列,以及这些列上1所在的行。
    我一开始没有remove第二次[鬼脸]
  2. dance里的remove和recover操作:
    死循环火葬场。最初把第二层循环的j=r[c],已经忘记当时是手误还是思路不清。
    Recover里r[l[c]]=l[r[c]]=c;这一步本来放在函数的开头,但是为了避免巨T,最终把它放在函数最后。
  3. 可恶的手误or分心
    输入i和j都\(\leq n\)了解一下……关键是你谷上面还能过4个点,剩下的TLE。虽然逼着我去想优化,但是……赔我两小时!

在写代码时加了如下优化

  1. 快读
  2. register int
  3. inline void
  4. 将dance函数从void变成bool,评测实测直接快了2.39s
  5. insert函数从循环\(1 \rightarrow C-1\)变成\(0 \rightarrow C\),简化了代码

DLX应用

数独类问题

我们每拿到一个题,应该考虑行和列所表示的意义:

行表示决策,因为每行对应着一个集合,也就对应着选/不选;
列表示状态,因为第\(i\)列对应着某个条件\(P_i\)

对于某一行而言,由于不同的列的值不尽相同,我们由不同的状态,定义了一个决策

学习资料

浅谈DLX
[DLX]舞蹈链数据结构介绍(一)精确覆盖问题
Dancing Links - OI Wiki