Dancing Links 详解
1.初始化部分:建立一行头节点
第0行建立好一排空的头节点,完善好十字链表的各项信息。
void init(){
for(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;
}
2.插入元素
每次在第0行下面插入,虽然直观上行的顺序打乱,实际上是翻转了一下,不影响拓扑结构
void 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;
l[idx]=hh,r[hh]=idx,r[idx]=tt,l[tt]=idx;
tt=idx++;
}
3.删除某一列 & 恢复某一列
(1) 在头结点中删除该列 (只删除右指针,左指针复原的时候要用,一般索引都是用右指针,所以删除右指针意味着逻辑上已经删除了,不会再被遍历到)
(2) 删除当前列意味着当前列已经被覆盖,因此凡是有1在这一列的行都不能被选择,可以全部删去。因此删去当前列所有1所在的行
(3) 恢复完全对称写即可,每次遍历使用d[]和r[]两个指针,因此恢复使用另外两个
void remove(int p){ //删除第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){ //恢复第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;
}
l[r[p]]=p,r[l[p]]=p;
}
4.深搜DFS+剪枝
剪枝有两种
(1) 每次枚举1最少的列
(2) remove中用到的,当前列能且只能被覆盖1次
借用OIwiki的一张图,这个剪枝效果还是十分明显的
[1]
bool dfs(){
if(!r[0]) return true;
int p=r[0];
for(int i=r[0];i;i=r[i])
if(s[i] < s[p]) p=i;
remove(p);
for(int i=d[p];i!=p;i=d[i]){
ans[++ top]=row[i];
for(int j=r[i];j!=i;j=r[j]) remove(col[j]);
if(dfs()) return true;
for(int j=l[i];j!=i;j=l[j]) resume(col[j]);
top --;
}
resume(p);
return false;
}
[1] 引用自 OI wiki https://oi-wiki.org/search/dlx/#__comments