舞蹈链解决精确覆盖问题
一、问题引入:
有n 个人, 每个人有一些想吃的菜. 只有你给这个人所有他想吃的菜,他才会吃.
可是你只有m 种菜, 每样一份.你必需把菜卖完. 问最多能满足多少人.
*精确覆盖问题的定义:给定一个由0-1组成的矩阵,是否能找到一个行的集合,使得集合中每一列都恰好包含一个1
假设有5种菜,4个人 ,1表示他喜欢吃这种菜。
(1)0 1 0 1 0
(2)1 0 0 0 0
(3)1 1 1 0 0
(4)0 0 1 0 1
可见最多满足第1 、2 、4。
答案得出的过程。
Step1:我们假设先满足第一人,那么第3人是不能选的(因为每种菜只有一份,3不能和1抢第二种菜)。
Step2: 接着选择第2个人,很完美,两人都可以满足。
Step3:接着选第4个人(第三个人在Step1就淘汰了。)也可以满足他。
这个例子太好了,换一下。
(1)0 0 1 0 1
(2)1 1 1 0 1
(3)0 0 1 1 1
(4)0 0 1 1 1
Step1:发现要覆盖第一列,必须满足第二个人。由于1.3.4人和2都喜欢吃第3种菜,所以都不能满足。
所以只能满足第二个人。但是这时菜不能完全卖完,此问题无解。
总结一下求解的过程:
(1)选择任意一行。
(2)删除选择这一行后不能选择的其他行。
(3)继续选择
(4)若最后没有可选择的行,但是没有完全覆盖,回到第(1)步,修改之前任意选择的行重复步骤。
到这挺好理解的...
二、
发现这过程需要存储矩阵和回溯的过程。那么怎样做比较高效呢。
舞蹈链是一种数据结构,缓存和回溯中效率惊人。不需要额外空间,接近于线性的时间,指针在数据之间
跳跃着,像舞蹈一样所以称之为舞蹈链。(dancing links).
舞蹈链用的数据结构是十字链表。
我们先从双向链表过渡到十字链表吧。
(1)双向链表
A1 A2 A3,
A1.right=A2,A2.left=A1,A3.right.right=A1.
在很多实际运用中,把双向链的首尾相连,构成循环双向链.
删除A2时,A1.right=A3,A3.left=A1,注意A2只是从双向链表中删除了,但是A2的left和right的信息并没有变。
由于不需要开空间,时间也是挺快的。
(2)接下来是双向十字链表。
每个元素有四个指针,left,right,up,down.
注意链首和链尾是双向连接的,一定要注意双向。
列首有我们虚构的点,在第0行,这是为了方便搜索,以后代码呈现。
三、用法。
删除和恢复元素。(先了解后看代码)
规则:
沿列删除时删除左右, 保留上下.
沿行删除时删除上下, 保留左右.
恢复时依然沿之前的方向, 根据自己的信息把自己插进去
四、
那么开始的问题引入,我们怎么用舞蹈链来解决呢。
........................................................假装你思考过了的样子.........................orz
首先每一行的行首表示每个人,列表示每个人爱吃的菜,然后求精确覆盖就可以啦。
五、code
当然很恶心。
int l[maxn],r[maxn],u[maxn],d[maxn],tn,ren[maxn],cai[maxn]; void shanchu(int x) { for(int p=r[x]; p!=x; p=r[p]) { for(int q=u[p]; q!=p; q=u[q]) { for(int s=r[q]; s!=q; s=r[s]) { if(d[x]!=s) { d[u[s]]=d[s]; u[d[s]]=u[s]; } } } } } void huifu(int x) { for(int p=l[x]; p!=x; p=l[p]) { for(int q=d[p]; q!=p; q=d[q]) { for(int s=l[q]; s!=q; s=l[s]) { u[d[s]]=d[u[s]]=s; } } } } int main() { memset(cai,0,sizeof(cai)); scanf("%d%d",&n,&m); ren[0]=tn=1; l[1]=r[1]=1; d[1]=u[1]=1; for(int i = 1; i <= n; i++) { ren[i]=++tn; l[tn]=r[tn]=tn;//左右方向建表 d[ren[i-1]]=tn;//上下方向 u[tn]=ren[i-1]; d[tn]=1;//指向列首。 u[1]=tn//列首指向列尾。 int totcai,caii,lastcai=tn; scanf("%d",&totcai); for(int j = 0; j < totcai; ++ j) { scanf("%d", &caii); ++tn; r[last = tn; l[tn] = lastcai; r[tn]=ren[i]; l[ren[i]]=tn; lastcai = tn; if(!cai[caii]) { //cai数组指的是caii最后一次出现的位置编号。 cai[caii]=u[tn]=d[tn]=tn; } else { u[tn]=cai[caii]; d[tn]=d[cai[caii]]; u[d[tn]]=tn; d[u[tn]]=tn; cai[caii]=tn; } } } }