CoinciDANCE——DLX学习笔记

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

  1. 需要声明的数组和变量
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<bits/stdc++.h>
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]<sv[c]) c=i;
	}
	rem(c);
	for(i=d[c]; i!=c; i=d[i]) {
		ans[tmp]=row[i];
		for(j=r[i]; j!=i; j=r[j]) rem(col[j]);
		if(dance(tmp+1)) return true;
		for(j=l[i]; j!=i; j=l[j]) rec(col[j]);
	}
	rec(c);
	return false;
}

inline void ins(const int &R,const int &C) {
	cnt++;
	row[cnt]=R; col[cnt]=C; sv[C]++;
	d[cnt]=d[C]; u[d[C]]=cnt;
	u[cnt]=C; d[C]=cnt;

	if(!hd[R]) hd[R]=l[cnt]=r[cnt]=cnt;
	else {
		r[cnt]=r[hd[R]]; l[r[hd[R]]]=cnt;
		l[cnt]=hd[R]; r[hd[R]]=cnt;
	}
}

inline void build(const int &R,const int &C) {
	for(register int i=0; i<=C; i++) {
		l[i]=i-1;
		r[i]=i+1;
		u[i]=d[i]=i;
	}
	l[0]=C;
	r[C]=0;
	cnt=C;
	memset(hd,0,sizeof(hd));
	memset(sv,0,sizeof(sv));
}

int main() {
	n=read();
	m=read();
	build(n,m);
	register int i,j;
	for(i=1; i<=n; i++) {
		for(j=1; j<=m; j++) {
			a=read();
			if(a) ins(i,j);
		}
	}
	if(dance(1)) {
		for(i=1; i<dep; i++) printf("%d ",ans[i]);
	} else printf("No Solution!");
	return 0;
}

在写代码时有如下错误

  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

posted @ 2022-03-10 21:06  Searshkiu  阅读(70)  评论(0编辑  收藏  举报