CoinciDANCE——DLX学习笔记
为了优雅地解决数独问题,我找到了优雅的Dancing Links。
DLX(Dancing Links + X Algorithm)简介
- 需要声明的数组和变量
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[]
- 在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;
}
在写代码时有如下错误
- dance操作:对Dancing Links理解不够深刻。
一共要进行两次remove操作:
第一次remove当前矩阵中含1个数最少的列,以及这些1所在的行;第二次remove这些1所在的行上的其它1所在的列,以及这些列上1所在的行。
我一开始没有remove第二次[鬼脸] - dance里的remove和recover操作:
死循环火葬场。最初把第二层循环的j=r[c]
,已经忘记当时是手误还是思路不清。
Recover里r[l[c]]=l[r[c]]=c;
这一步本来放在函数的开头,但是为了避免巨T,最终把它放在函数最后。 - 可恶的手误or分心!
输入i和j都\(\leq n\)了解一下……关键是你谷上面还能过4个点,剩下的TLE。虽然逼着我去想优化,但是……赔我两小时!
在写代码时加了如下优化
- 快读
- register int
- inline void
- 将dance函数从void变成bool,评测实测直接快了2.39s
- insert函数从循环\(1 \rightarrow C-1\)变成\(0 \rightarrow C\),简化了代码
DLX应用
数独类问题
我们每拿到一个题,应该考虑行和列所表示的意义:
行表示决策,因为每行对应着一个集合,也就对应着选/不选;
列表示状态,因为第\(i\)列对应着某个条件\(P_i\)。对于某一行而言,由于不同的列的值不尽相同,我们由不同的状态,定义了一个决策。