DLX--舞蹈链详解

DLX大意:通过双向链表高效维护爆搜的搜索与回溯解决完全覆盖问题

感觉就是很暴力的一种思想,是个人都想得到


前置芝士:不重复与重复完全覆盖问题
  1. 不重复完全覆盖问题
    我们有一些工具人,他们的工作时间各不相同,我们要选择几个人使得每天都有且仅有一个人在工作。

    显然,我们选择人B,E,F可以满足条件。
    这种选择几种方案并使得区域完全覆盖且不重复的就是不重复完全覆盖问题。

  2. 重复完全覆盖问题
    对于上面的图显而易见就是ABCDEF都选。
    这种问题一般要求方案最少的数目。
    这种选择几种方案并使得区域完全覆盖可以重复的就是重复完全覆盖问题。


算法:

其实挺简单的。
在这里插入图片描述
还是这个图。
我们随便选一列(天),不妨选星期一,然后选中第一个该列为1的行A作为第一个工具
然后把行A和行A中所有为1的列在整个表中删除,效果如下:
在这里插入图片描述
明显A已经失去作用,所以放置play扔掉(工具人本性
并且A所可以工作的日子不需要再讨论。

以上对于重复和不重复完全覆盖问题都是相同的,以下有一点不同。

分支1-1:对于重复完全覆盖问题,我们什么都不干,只是删掉列即可,效果如上图。

分支1-2:对于不重复完全覆盖问题,我们需要将A所可以工作的列 该列状态为1的行 在整个表中删掉(不可重复),听起来有点拗口,其实就是去重,会长这样:
在这里插入图片描述
人全没了,代表你失败了,此时需要回溯。

因为1-1状态太多,不再赘述,继续深入1-2。
我们回溯到原图:
在这里插入图片描述
行A已经验证过不可行,我们就是同样星期一有空的行B。
一波操作后如下图:
在这里插入图片描述
再选一列,不妨为星期二(都是第一个,只能选E,再一波操作:
在这里插入图片描述
一个F解决所有问题,找到了一组可行解,返回。

这玩意就是X算法,毫无难度。

Dancing Links用于维护这个删行列和恢复行列的操作,其实就是双向链表,一个上下左右的联系。
就可以便捷地修改原表了!


技巧:

很容易想到在选择列时选择点最少的那一列,能够减少枚举次数。
某些题目可以利用IDA* 来优化DLX。
IDA*可以看看另一篇blog:https://blog.csdn.net/tylon2006/article/details/103967989


代码:

加个表头方便删除和查询。
若dfs中不使用表可以只删表头,使用则要整列删除,但要保留该行连续。

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
struct data{
	int l,r,u,d,col,row;//左,右,上,下,所在列,所在行。
	void make(int L,int R,int U,int D,int Col,int Row){
		l=L,r=R,u=U,d=D,col=Col,row=Row;
	}
};
int a[10];//存储新建行的点
struct DLX{
	data p[200010];//点
	int siz[2010],last[2010],cnt;//每列点数,每列最后的点编号(便于加点),总点数
	void delc(int x){//删点的上下关系
		p[p[x].u].d=p[x].d;
		p[p[x].d].u=p[x].u;
	}
	void delr(int x){//删点的左右关系
		p[p[x].l].r=p[x].r;
		p[p[x].r].l=p[x].l;
	}
	void linkc(int x){//恢复点的上下关系
		p[p[x].u].d=p[p[x].d].u=x;
	}
	void linkr(int x){//恢复点的左右关系
		p[p[x].l].r=p[p[x].r].l=x;
	} 
	void clear(int x){//清空,建出所有列的列头
		cnt=0;
		p[0].l=p[0].r=0;
		p[0].u=p[0].d=0;//head=0用于遍历
		for(int i=1;i<=x;i++){
			cnt++,p[cnt].make(i-1,p[i-1].r,i,i,i,0);
			linkr(i),siz[i]=0,last[i]=i;
		}
	}
	void add(int x){//添加行x
		if(a[0]==0) return;
		cnt++,p[cnt].make(cnt,cnt,last[a[1]],p[last[a[1]]].d,a[1],x);
		linkc(cnt),siz[a[1]]++,last[a[1]]=cnt;
		for(int i=2;i<=a[0];i++){
			cnt++,p[cnt].make(cnt-1,p[cnt-1].r,last[a[i]],p[last[a[i]]].d,a[i],x);
			linkc(cnt),linkr(cnt),siz[a[i]]++,last[a[i]]=cnt;
		}
	}
	void del(int x){//删列x
		//可重复覆盖
		delr(x);
	}
	/*
	void del(int x){
		/*不重复覆盖问题
		delr(x);
		for(int i=p[x].u;i!=x;i=p[i].u)
		for(int j=p[i].r;j!=i;j=p[j].r)
		delc(j),siz[p[j].col]--;
	}
	void del(int x){
		//需要使用表
		for(int i=p[x].d;i!=x;i=p[i].d) delr(i);
	}
	*/
	void resume(int x){//恢复列x 就是反过来
		//可重复覆盖
		linkr(x);
	}
	/*
	void resume(int x){
		//不重复覆盖问题
		linkr(x);
		for(int i=p[x].d;i!=x;i=p[i].d)
		for(int j=p[i].l;j!=i;j=p[j].l)
		linkc(j),siz[p[j].col]++;
	}
	void resume(int x){
		//需要使用表
		for(int i=p[x].d;i!=x;i=p[i].d) linkr(i);
	}
	*/
	bool dance(int x){//跳舞,即找答案
		if(!p[0].r){
			//记录和输出答案
			return true;
		}
		int pos=0,minn=2e9;
		for(int i=p[0].r;i;i=p[i].r)
		if(siz[p[i].col]<minn){//找点数最小列
			minn=siz[p[i].col];
			pos=p[i].col;
		}
		if(minn==0) return false;//如果有列没有行可以覆盖,则无解,返回0
		//不需要使用表
		del(pos);//先删掉该列
		for(int i=p[pos].u;i!=pos;i=p[i].u){
			for(int j=p[i].l;j!=i;j=p[j].l) del(p[j].col);
			ans[x+1].work(p[i].row);
			if(dance(x+1)) return true;//找到解才返回1
			for(int j=p[i].r;j!=i;j=p[j].r) resume(p[j].col);
		}
		resume(pos);//恢复该列
		/*
		//需要使用表
		for(int i=p[pos].u;i!=pos;i=p[i].u){
			del(i);
			for(int j=p[i].l;j!=i;j=p[j].l) del(j);//,cout<<j<<endl;
			dance(x+1);
			for(int j=p[i].r;j!=i;j=p[j].r) resume(j);       
			resume(i);
		}
		*/
		return false;
	}
}dlx;
int main(){
	dlx.clear(/*列数*/);
}

例题:

poj3076
poj1084(IDA*优化DLX

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章