abastract:利用dancing links 解決精確覆蓋問題,例如數獨,n皇后問題;以及重復覆蓋問題。
要學習dacning links 算法,首先要先了解該算法適用的問題,精確覆蓋問題和重復覆蓋問題等,下面先了解精確覆蓋問題和重復覆蓋問題。
精確覆蓋問題
何為精確覆蓋問題
在一個全集X中若干子集的集合為S,精確覆蓋(Exactcover)是指,S的子集S*,滿足X中的每一個元素在S*中恰好出現一次。
定義
S*中任意兩個集合沒有交集,即X中的元素在S*中出現最多一次
S*中集合的全集為X,即X中的元素在S*中出現最少一次
合二為一,即X中的元素在S*中出現恰好一次。
N={}
O={1,3}
E={2,4}
P={2,3}.
其中一個子集{O,E}是X的一個精確覆蓋,因為O={1,3}而E={2,4}的並集恰好是X={1,2,3,4}。同理,{N,O,E}也是X.的一個精確覆蓋。空集並不影響結論。
精確覆蓋問題的表示方式
一般的,我們用一個集合s包含s中的元素的單向關系表示精確覆蓋問題。常用的有以下兩種方法:
- 矩陣表示法
包含關系可以用一個關系矩陣表示。.矩陣每行表示S的一個子集,每列表示X中的一個元素。矩陣行列交點元素為1表示對應的元素在對應的集合中,不在則為0。
通過這種矩陣表示法,求一個精確覆蓋轉化為求矩陣的若干個行的集合,使每列有且僅有一個1。同時,該問題也是精確覆蓋的典型例題之一。
下表為其中一個例子:
S*={B,D,F}便是一個精確覆蓋。
- 圖論表示法
可將精確覆蓋問題轉化為一個二分圖,左側為集合,右側為元素,左側集合若與右側元素有包含關系則連邊,通過將左側節點與其所有邊保留與否求解一個右側的每一個節點恰好有一條邊的匹配。
重復覆蓋問題
即選取一個01矩陣中的幾行,使這幾行組成的新矩陣的每一列至少有一個1。 該問題在精確覆蓋問題上減少了一個約束條件。
接下來就是dancing links x算法了。
Dancing Links X 算法
歷史
X算法是高德納提出的解決精確覆蓋問題的算法,而dancing links X算法則是DonKnuth(《計算機程序設計藝術》的作者)提出的對X算法的一種高效實現,這種實現建立在如上所說的矩陣表示法上。
算法思想
由如上精確覆蓋問題的矩陣表示法中,我們知道dancing links x 是用來求解一個 01矩陣中選取哪幾行可以使得這幾行每一列都有且僅有一個1(就是每個元素在這幾個子集里有且僅有出現過一次)。
先不管他的實際意義,我們需要做的就是在一個01矩陣的選取某幾行使之符合上述條件。
我們很容易就想到枚舉,然后判斷符不符合條件,但是這個做法實在是太消耗時間。
dacing links x就是一個高效的求解該類問題的算法,而這種算法,基於交叉十字循環雙向鏈的數據結構。
例如:如下的矩陣
就包含了這樣一個集合(第1、4、5行)
如何利用給定的矩陣求出相應的行的集合呢?我們采用回溯法
先假定選擇第1行,如下所示:
如上圖中所示,紅色的那行是選中的一行,這一行中有3個1,分別是第3、5、6列。
由於這3列已經包含了1,故,把這三列往下標示,圖中的藍色部分。藍色部分包含3個1,分別在2行中,把這2行用紫色標示出來
根據定義,同一列的1只能有1個,故紫色的兩行,和紅色的一行的1相沖突。
那么在接下來的求解中,紅色的部分、藍色的部分、紫色的部分都不能用了,把這些部分都刪除,得到一個新的矩陣
行分別對應矩陣1中的第2、4、5行
列分別對應矩陣1中的第1、2、4、7列
於是問題就轉換為一個規模小點的精確覆蓋問題
在新的矩陣中再選擇第1行,如下圖所示
還是按照之前的步驟,進行標示。紅色、藍色和紫色的部分又全都刪除,導致新的空矩陣產生,而紅色的一行中有0(有0就說明這一列沒有1覆蓋)。說明,第1行選擇是錯誤的
那么回到之前,選擇第2行,如下圖所示
按照之前的步驟,進行標示。把紅色、藍色、紫色部分刪除后,得到新的矩陣
行對應矩陣2中的第3行,矩陣1中的第5行
列對應矩陣2中的第2、4列,矩陣1中的第2、7列
由於剩下的矩陣只有1行,且都是1,選擇這一行,問題就解決
於是該問題的解就是矩陣1中第1行、矩陣2中的第2行、矩陣3中的第1行。也就是矩陣1中的第1、4、5行()
(例子引用自http://www.cnblogs.com/grenet/p/3145800.html)
而對於重復覆蓋問題,在選定某一行之后只需刪除該行含1的所在列,並不需要再刪除所在列上含1的行。
2016-08-21 21:00:15
Example1:HUST 1017
裸精確覆蓋問題,問題如下:
1017 - Exact cover
題目描述:
There is an N*M matrix with only 0s and 1s, (1 <= N,M <= 1000). An exact cover is a selection of rows such that every column has a 1 in exactly one of the selected rows. Try to find out the selected rows.輸入There are multiply test cases. First line: two integers N, M; The following N lines: Every line first comes an integer C(1 <= C <= 100), represents the number of 1s in this row, then comes C integers: the index of the columns whose value is 1 in this row.輸出First output the number of rows in the selection, then output the index of the selected rows. If there are multiply selections, you should just output any of them. If there are no selection, just output "NO".
樣例輸入
6 7
3 1 4 7
2 1 4
3 4 5 7
3 3 5 6
4 2 3 6 7
2 2 7
樣例輸出
3 2 4 6
提示來源
dupeng
裸精確覆蓋問題,直接用dancing links 做。
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #define clr(x) memset(x,0,sizeof(x)) 5 #define clrlow(x) memset(x,-1,sizeof(x)) 6 #define maxnode 1001010 7 #define maxn 1010 8 using namespace std; 9 struct DLX{ 10 int n,m; 11 int U[maxnode],D[maxnode],L[maxnode],R[maxnode],col[maxnode],row[maxnode]; 12 int H[maxn]; 13 int ansed,ans[maxn],size; 14 void init(int _n,int _m) 15 { 16 n=_n; 17 m=_m; 18 for(int i=0;i<=m;i++) 19 { 20 U[i]=i; 21 D[i]=i; 22 L[i]=i-1; 23 R[i]=i+1; 24 col[i]=i; 25 row[i]=0; 26 } 27 L[0]=m; 28 R[m]=0; 29 size=m; 30 clrlow(H); 31 clr(ans); 32 ansed=0; 33 return ; 34 } 35 void push(int r,int c) 36 { 37 size++; 38 D[size]=D[c]; 39 U[size]=c; 40 U[D[c]]=size; 41 D[c]=size; 42 row[size]=r; 43 col[size]=c; 44 if(H[r]<0) 45 { 46 H[r]=size; 47 R[size]=L[size]=size; 48 } 49 else 50 { 51 L[size]=H[r]; 52 R[size]=R[H[r]]; 53 L[R[H[r]]]=size; 54 R[H[r]]=size; 55 } 56 } 57 void del(int c) 58 { 59 R[L[c]]=R[c]; 60 L[R[c]]=L[c]; 61 for(int i=D[c];i!=c;i=D[i]) 62 for(int j=R[i];j!=i;j=R[j]) 63 { 64 D[U[j]]=D[j]; 65 U[D[j]]=U[j]; 66 } 67 return ; 68 } 69 void reback(int c) 70 { 71 for(int i=U[c];i!=c;i=U[i]) 72 for(int j=L[i];j!=i;j=L[j]) 73 { 74 D[U[j]]=j; 75 U[D[j]]=j; 76 } 77 R[L[c]]=c; 78 L[R[c]]=c; 79 return ; 80 } 81 bool dancing(int dep) 82 { 83 if(R[0]==0) 84 { 85 ansed=dep; 86 return true; 87 } 88 int c=R[0]; 89 del(c); 90 for(int i=D[c];i!=c;i=D[i]) 91 { 92 ans[dep]=row[i]; 93 for(int j=R[i];j!=i;j=R[j]) 94 del(col[j]); 95 if(dancing(dep+1)) 96 return true; 97 for(int j=L[i];j!=i;j=L[j]) 98 reback(col[j]); 99 } 100 return false; 101 } 102 }dlx; 103 int main() 104 { 105 int n,m,p,k; 106 while(scanf("%d%d",&n,&m)==2) 107 { 108 dlx.init(n,m); 109 for(int i=1;i<=n;i++) 110 { 111 scanf("%d",&p); 112 for(int j=1;j<=p;j++) 113 { 114 scanf("%d",&k); 115 dlx.push(i,k); 116 } 117 } 118 if(!dlx.dancing(0)) 119 printf("NO\n"); 120 else 121 { 122 printf("%d",dlx.ansed); 123 for(int i=0;i<dlx.ansed;i++) 124 printf(" %d",dlx.ans[i]); 125 printf("\n"); 126 } 127 } 128 return 0; 129 }
除此之外,運用這個dancing links 的這個模板,還可以解決數獨,n皇后問題。
淺談數獨解法
我們在做數獨時一般會使用枚舉法。在某個格子枚舉當前情況下的所有可填入數字,而在枚舉其中一個可填入數字后遞歸到下一層,也就是下一個格子,枚舉上一個格子數字確定下來的九宮格在該格子的所有可填入數字,以此類推。直到九宮格完全填滿時,這時候的解為該九宮格的一個可行解,可繼續遞歸返回上一層尋找下一個可行解。這個做法效率不錯,但是若直接枚舉,寫起來就又臭又長(好吧我承認其實DLX也挺長的),若九宮格過於稀疏,該做法的時間效率也會指數級上升。我們選擇用DLX把每一個已填格子和未填格子的信息轉化成行,接下來dance一遍找出符合條件的行,再轉化回九宮格信息,就是一個可行解了。最多729行進行DLX(9*9*9,后面你會明白的),速度較直接枚舉也快很多,除去DLX的結構體,其他代碼長度也比直接枚舉短很多(打模板比較快hhh)。
那么怎么把九宮格上的信息轉化成行呢? 首先數獨中可行的解需滿足:每一行,每一列,每一個宮里面不能有相同的數字,且只能使用1-9這九個數字。
把它轉化為符合精確覆蓋問題的條件,即為:
- 每一個格只能填一個數字。
- 每個數字在每一行只能出現一次。
- 每個數字在每一列只能出現一次。
- 每個數字在每一宮只能出現一次。
對於第一個條件:
第一列表示填入(1,1)這一格。
第二列表示填入(1,2)這一格。
第三列表示填入(1,3)這一格。
……
第(m-1)*9+n列表示填入(m,n)這一格。(1<=m<=9,1<=n<=9)
……
第9*9-1列表示填入(9,8)這一格。
第9*9列表示填入(9,9)這一格。
設inf1=81;
以此類推……
第二個條件:
第inf+1列表示在第一行填入1。
第inf+2列表示在第一行填入2。
第inf+3列表示在第一行填入3。
……
第inf+(n-1)*9+m列表示在第n行填入m(1<=n<=9,1<=m<=9)。
……
第inf+9*9表示在第九行填入9。
設inf2=inf1+81;
對於第三個條件也是如此,inf3=inf2+81;
對於第四個條件,inf4=inf3+81;
至此,已經把所有的條件都轉化成了列。
而對於一個有數字的格子(假設位於(m,n)數字為k,位於第p宮)只需把它轉化為一行,該行於(m-1)*9+n,inf1+(m-1)*9+k,inf2+(n-1)*9+k,inf3+(p-1)*9+k這四列為1,其余為0。
對於一個沒有數字的格子(假設位於(m',n')第p'宮),則需把他轉化為九行,分別代表在這一格填入1-9各個數字,每一列的填列原理和有數字的格子一樣。
在把所有的格子轉化完畢后,先把所有確定的有數字的格子的行以及該行含1的列以及該列上含1的行從矩陣中刪除,然后dance一遍,找出符合的行,再把行轉化成每個格子的信息,填入九宮格中,就是符合的一個解了。
2016-08-27 17:07:53
n皇后解法
類似於數獨問題,在n皇后問題中,不沖突的n皇后的棋盤上符合以下五個個條件:
- 每一個格子最多只能有一個皇后。
- 每一行最多只能有一個皇后。
- 每一列最多只能有一個皇后。
- 每一個從左上往右下的斜邊最多只能有一個皇后。
- 每一個從右上往左下的斜邊最多只能有一個皇后。
這五個條件看起來不太好用dlx的做法去做,但如果把整個棋盤中有皇后的位置用1表示,沒有皇后的位置用1表示,則可以轉化為:
- 每個格子只能填一個數字。
- 每一行有且僅能填一個1。
- 每一列有且僅能填一個1。
- 每一個從左上往右下的斜邊有且僅能填一個1。
- 每一個從右上往左下的斜邊有且僅能填一個1。
按照數獨的過程進行DLX,找出填1的行數大於等於n的解。等於n的解即為n皇后問題的解,大於n的解刪去幾行使填1的行數為n即為n皇后問題的解。
重復覆蓋問題
顧名思義,即為在01矩陣中,選出幾行,使得在這幾行組成的新矩陣中,每一列都有1。
下面是hdu上一個重復覆蓋的二分問題,並且給出重復覆蓋問題的代碼部分:
Example2:hdu 2295
Radar
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 2280 Accepted Submission(s): 897
Each of the last M lines consists of the coordinate of a radar station.
All coordinates are separated by one space.
Technical Specification
1. 1 ≤ T ≤ 20
2. 1 ≤ N, M ≤ 50
3. 1 ≤ K ≤ M
4. 0 ≤ X, Y ≤ 1000
該題用二分查找radar的半徑,然后用重復覆蓋的DLX進行最多選取k行該半徑是否完全覆蓋所有城市的判斷(f()函數和dep一起判斷),最后精確到小數點后六位。
#include<cstdio> #include<iostream> #include<cstring> #include<cmath> #define clr(x) memset(x,0,sizeof(x)) #define clrlow(x) memset(x,-1,sizeof(x)) #define maxnode 3000 #define maxn 60 using namespace std; struct DLX//dancing links { int U[maxnode],D[maxnode],L[maxnode],R[maxnode],col[maxnode],row[maxnode];//元素上下左右對應列對應行的指針 int S[maxn],H[maxn],V[maxn];//S為每列元素個數,H指向每行末尾的元素,V是dep()函數的標記數組。 int n,m,size,all;//all為解的行數的最大值 void init(int _n,int _m,int _all) { n=_n; m=_m; all=_all; for(int i=0;i<=m;i++) { L[i]=i-1; R[i]=i+1; U[i]=i; D[i]=i; row[i]=0; col[i]=i; } clr(S); clrlow(H); L[0]=m; R[m]=0; size=m; return ; } //初始化 void push(int r,int c) { D[++size]=D[c]; col[size]=U[size]=c; U[D[c]]=size; D[c]=size; row[size]=r; S[c]++; if(H[r]<0) { H[r]=L[size]=R[size]=size; } else { L[size]=H[r]; R[size]=R[H[r]]; L[R[H[r]]]=size; R[H[r]]=size; } return ; } //加入元素 void del(int c) { S[col[c]]--; for(int i=D[c];i!=c;i=D[i]) { R[L[i]]=R[i]; L[R[i]]=L[i]; S[col[i]]--; } return ; } //刪除一列 void reback(int c) { for(int i=U[c];i!=c;i=U[i]) { S[col[R[L[i]]=L[R[i]]=i]]++; } S[col[c]]++; return ; } //恢復一列 int dep( ) { clr(V); int deep=0; for(int i=R[0];i!=0;i=R[i]) if(!V[i]) { deep++; for(int j=D[i];j!=i;j=D[j]) for(int k=R[j];k!=j;k=R[k]) V[col[k]]=1; } return deep; } //之后到達的最大深度 //d為當前深度 bool dancing(int d) { if(d+dep()>all) return false; int c=R[0]; if(c==0) { return d<=all; } for(int i=R[0];i!=0;i=R[i]) if(S[i]<S[c]) c=i; for(int i=D[c];i!=c;i=D[i]) { del(i); for(int j=R[i];j!=i;j=R[j]) del(j); if(dancing(d+1)) return true; for(int j=L[i];j!=i;j=L[j]) reback(j); reback(i); } return false; } //dancing }dlx; struct point { int x,y; }station[maxn],city[maxn]; double dis(point a,point b) { return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y); } int main() { int n,m,k,kase; double lt,rt,mid; double eps=1e-8; scanf("%d",&kase); while(kase--) { scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=n;i++) scanf("%d%d",&city[i].x,&city[i].y); for(int i=1;i<=m;i++) scanf("%d%d",&station[i].x,&station[i].y); lt=0; rt=1e8; while(rt-lt>=eps) { dlx.init(m,n,k); mid=(rt+lt)/2; for(int i=1;i<=m;i++) for(int j=1;j<=n;j++) if(dis(city[j],station[i])<mid*mid-eps) dlx.push(i,j); if(dlx.dancing(0)) rt=mid-eps; else lt=mid+eps; } printf("%.6lf\n",lt); } return 0; }
FZU上一道練手題
Problem 1686 神龍的難題
Accept: 717 Submit: 2140
Time Limit: 1000 mSec Memory Limit : 32768 KB
Problem Description
這是個劍與魔法的世界.英雄和魔物同在,動盪和安定並存.但總的來說,庫爾特王國是個安寧的國家,人民安居樂業,魔物也比較少.但是.總有一些魔物不時會進入城市附近,干擾人民的生活.就要有一些人出來守護居民們不被魔物侵害.魔法使艾米莉就是這樣的一個人.她騎着她的坐騎,神龍米格拉一起消滅干擾人類生存的魔物,維護王國的安定.艾米莉希望能夠在損傷最小的前提下完成任務.每次戰斗前,她都用時間停止魔法停住時間,然后米格拉他就可以發出火球燒死敵人.米格拉想知道,他如何以最快的速度消滅敵人,減輕艾米莉的負擔.
Input
數據有多組,你要處理到EOF為止.每組數據第一行有兩個數,n,m,(1<=n,m<=15)表示這次任務的地區范圍. 然后接下來有n行,每行m個整數,如為1表示該點有怪物,為0表示該點無怪物.然后接下一行有兩個整數,n1,m1 (n1<=n,m1<=m)分別表示米格拉一次能攻擊的行,列數(行列不能互換),假設米格拉一單位時間能發出一個火球,所有怪物都可一擊必殺.
Output
輸出一行,一個整數,表示米格拉消滅所有魔物的最短時間.
Sample Input
Sample Output
Source
FOJ月賽-2009年2月- TimeLoop
依舊是重復覆蓋問題模板
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #define clr(x) memset(x,0,sizeof(x)) 5 #define clrlow(x) memset(x,-1,sizeof(x)) 6 #define maxm (15*15+10) 7 #define maxn (15*15+10) 8 #define maxnode maxn*maxm 9 #define INF 1000000000 10 using namespace std; 11 struct DLX 12 { 13 int size,n,m; 14 int D[maxnode],U[maxnode],L[maxnode],R[maxnode],row[maxnode],col[maxnode]; 15 int H[maxn],S[maxm]; 16 int ansed; 17 void init(int _n,int _m) 18 { 19 n=_n; 20 m=_m; 21 size=m; 22 for(int i=0;i<=m;i++) 23 { 24 D[i]=i; 25 U[i]=i; 26 R[i]=i+1; 27 L[i]=i-1; 28 row[i]=0; 29 col[i]=i; 30 S[i]=0; 31 } 32 L[0]=m; R[m]=0; 33 for(int i = 1;i <= n;i++)H[i] = -1; 34 ansed=INF; 35 return ; 36 } 37 void push(int r,int c) 38 { 39 ++S[col[++size]=c]; 40 row[size]=r; 41 D[size]=D[c]; 42 U[size]=c; 43 U[D[c]]=size; 44 D[c]=size; 45 if(H[r]<0) 46 { 47 L[size]=R[size]=size; 48 H[r]=size; 49 } 50 else 51 { 52 R[size]=R[H[r]]; 53 L[size]=H[r]; 54 L[R[H[r]]]=size; 55 R[H[r]]=size; 56 } 57 return; 58 } 59 void del(int p) 60 { 61 for(int i=D[p];i!=p;i=D[i]) 62 { 63 R[L[i]]=R[i]; 64 L[R[i]]=L[i]; 65 } 66 return ; 67 } 68 void remove(int p) 69 { 70 for(int i=U[p];i!=p;i=U[i]) 71 { 72 R[L[i]]=L[R[i]]=i; 73 } 74 return ; 75 } 76 bool v[maxm]; 77 int dis() 78 { 79 int ans=0; 80 for(int i=1;i<=m;i++) v[i]=0; 81 for(int i=R[0];i!=0;i=R[i]) 82 if(!v[i]) 83 { 84 ans++; 85 for(int j=D[i];j!=i;j=D[j]) 86 for(int k=R[j];k!=j;k=R[k]) 87 v[col[k]]=1; 88 } 89 return ans; 90 } 91 void dancing(int dep) 92 { 93 if(dep+dis()>=ansed) return; 94 if(R[0]==0) 95 { 96 if(dep<ansed) ansed=dep; 97 return ; 98 } 99 int c=R[0]; 100 for(int i=R[0];i!=0;i=R[i]) 101 if(S[i]<S[c]) 102 c=i; 103 for(int i=D[c];i!=c;i=D[i]) 104 { 105 del(i); 106 for(int j=R[i];j!=i;j=R[j]) 107 del(j); 108 dancing(dep+1); 109 for(int j=L[i];j!=i;j=L[j]) 110 remove(j); 111 remove(i); 112 } 113 return ; 114 } 115 }dlx; 116 int n,m,n1,m1,a[maxn][maxm],num[maxn][maxm],inf; 117 int main() 118 { 119 while(scanf("%d%d",&n,&m)!=EOF) 120 { 121 inf=0; 122 clr(a); 123 clr(num); 124 for(int i=1;i<=n;i++) 125 for(int j=1;j<=m;j++) 126 { 127 scanf("%d",&a[i][j]); 128 if(a[i][j]==1) num[i][j]=(++inf); 129 } 130 dlx.init(n*m,inf); 131 scanf("%d%d",&n1,&m1); 132 inf=0; 133 for(int i=1;i<=n;i++) 134 for(int j=1;j<=m;j++) 135 { 136 inf++; 137 for(int k=i;k<i+n1 && k<=n;k++) 138 for(int l=j;l<j+m1 && l<=m;l++) 139 if(num[k][l]) 140 dlx.push(inf,num[k][l]); 141 } 142 dlx.dancing(0); 143 printf("%d\n",dlx.ansed); 144 } 145 return 0; 146 }