Dancing Links X(DLX)
作用:优化搜索
题型:
- 精确覆盖
- 重复覆盖(配合IDA*)

前提:1的个数较少
存储:十字链表:每个数指向上下左右第一个元素

1 | int l[N],r[N],u[N],d[N],s[N],row[N],col[N],idx; |
初始化:创建第零行(第$i$个的左指针为$i-1$,右指针为$i+1$,上下指针为自己
1
2
3
4
5for(int i=0;i<=m;i++){
l[i]=i-1,r[i]=i+1,u[i]=d[i]=i;
}
l[0]=m,r[m]=0;//特判端点
idx=m+1;//一共用了m+1个点每次加入一行
维护当前行的第一个点$hh,tt$(初始均为$idx$),每次插入一个点时插在$hh,tt$之间,修改对应关系,修改$hh$

1
2
3
4
5
6int add(int &hh,int &tt,int x,int y){
row[idx]=x,col[idx]=y,s[y]++;
u[idx]=y,d[idx]=d[y],u[d[y]]=idx,d[y]=idx;
r[hh]=idx,l[tt]=idx,l[idx]=hh,r[idx]=tt;
tt=idx++;
}可以发现,建立完十字链表后可以用$d$从上到下查找,用$r$从左到右查找,用$l$从右到左查找
精确覆盖问题
给定一个 $ N \times M $ 的数字矩阵 $ A $,矩阵中的元素 $ A_{i,j} \in \lbrace 0,1 \rbrace $。
请问,你能否在矩阵中找到一个行的集合,使得这些行中,每一列都有且仅有一个数字 $ 1 $。
dfs:任意从未选择的行中选择一行
剪枝:
选择1的个数最少的一列,枚举当前列有1的行
1
2
3
4
5
6
7
8
9if(!r[0])return true;//若所有列都被删除,则已经找完所有列
int p=r[0];
for(int i=r[0];i;i=r[i]){
if(s[i]<=s[p])p=i;
}
//do something
for(int i=d[p];i!=p;i=d[i]){
//do something
}删除第$p$列
可以发现,查找列时只会在第0列查找,所以只需删除第0列中的当前行,并在每个1所在的行删除(只需删除纵向即可跳过遍历)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29void remove(int p){
r[l[p]]=r[p];l[r[p]]=l[p];
for(int i=d[p];i!=p;i=d[i]){
for(int j=r[i];j!=i;j=r[j]){
s[col[j]]--;
u[d[j]]=u[j];d[u[j]]=d[j];
}
}
}
void resume(int p){//逆方向恢复
r[l[p]]=p;l[r[p]]=p;
for(int i=u[p];i!=p;i=u[i]){
for(int j=l[i];j!=i;j=l[j]){
s[col[j]]++;
u[d[j]]=j;d[u[j]]=j;
}
}
}
bool dfs(){
...
remove(p);
for(int i=d[p];i!=p;i=d[i]){
...
}
resume(p);
...
}删除选择的行中所有有1的列
1
2
3
4
5
6
7
8
9
10bool dfs(){
...
for(int i=d[p];i!=p;i=d[i]){
...
for(int j=r[i];j!=i;j=r[j])remove(col[j]);
...
for(int j=l[i];j!=i;j=l[j])resume(col[j]);//反向恢复
}
...
}
总代码:
1 |
|
重复覆盖问题
给定一个 $ N \times M $ 的数字矩阵 $ A $,矩阵中的元素 $ A_{i,j} \in {0,1} $。
请你在矩阵中找到一个行的集合,使得这些行中,每一列都包含数字 $ 1 $,并且集合中包含的行数尽可能少。
dfs:
选择一个空列(选择行数最少的列)
枚举当前列是1的行
枚举选当前行
递归
