淺談舞蹈鏈
舞蹈鏈解決精確覆蓋問題
一、問題引入:
有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; } } } }
淺談舞蹈鏈