一些連接:html
http://www.cnblogs.com/-sunshine/p/3358922.htmlnode
http://www.cnblogs.com/grenet/p/3145800.htmlios
一、hust 1017 Exact cover (Dancing Links 模板題) ide
題意:n*m的單位矩陣。如今要選一些行,使得這些行的集合中每列只出現一個1.函數
思路:裸的精確覆蓋問題。刷一遍模板。優化
1 #include <iostream> 2 #include <stdio.h> 3 #include <string.h> 4 //精確覆蓋問題的定義:給定一個由0-1組成的矩陣,是否能找到一個行的集合,使得集合中每一列都剛好包含一個1 5 const int MN = 1005;//最大行數 6 const int MM = 1005;//最大列數 7 const int MNN = 1e5 + 5 + MM; //最大點數 8 9 struct DLX 10 { 11 int n, m, si;//n行數m列數si目前有的節點數 12 //十字鏈表組成部分 13 int U[MNN], D[MNN], L[MNN], R[MNN], Row[MNN], Col[MNN]; 14 //第i個結點的U向上指針D下L左R右,所在位置Row行Col列 15 int H[MN], S[MM]; //記錄行的選擇狀況和列的覆蓋狀況 16 int ansd, ans[MN]; 17 void init(int _n, int _m) //初始化空表 18 { 19 n = _n; 20 m = _m; 21 for (int i = 0; i <= m; i++) //初始化第一橫行(表頭) 22 { 23 S[i] = 0; 24 U[i] = D[i] = i; //目前縱向的鏈是空的 25 L[i] = i - 1; 26 R[i] = i + 1; //橫向的連起來 27 } 28 R[m] = 0; L[0] = m; 29 si = m; //目前用了前0~m個結點 30 for (int i = 1; i <= n; i++) 31 H[i] = -1; 32 } 33 void link(int r, int c) //插入點(r,c) 34 { 35 ++S[Col[++si] = c]; //si++;Col[si]=c;S[c]++; 36 Row[si] = r;//si該結點的行數爲r 37 D[si] = D[c];//向下指向c的下面的第一個結點 38 U[D[c]] = si;//c的下面的第一個結點的上面爲si 39 U[si] = c;//si的上面爲列指針 40 D[c] = si;//列指針指向的第一個該列中的元素設爲si 41 if (H[r]<0)//若是第r行沒有元素 42 H[r] = L[si] = R[si] = si; 43 else 44 { 45 R[si] = R[H[r]];//si的右邊爲行指針所指的右邊第一個元素 46 L[R[H[r]]] = si;//行指針所指的右邊第一個元素的左側爲si 47 L[si] = H[r];//si的左側爲行指針 48 R[H[r]] = si;//行指針的右側爲si 49 } 50 } 51 void remove(int c) //列表中刪掉c列 52 { 53 L[R[c]] = L[c];//表頭操做 //c列頭指針的右邊的元素的左側指向c列頭指針左邊的元素 54 R[L[c]] = R[c];//c列頭指針的左邊的元素的右側指向c列頭指針右邊的元素 55 for (int i = D[c]; i != c; i = D[i])//遍歷該列的全部元素 56 for (int j = R[i]; j != i; j = R[j]) 57 {//對於該列的某個元素所在的行進行遍歷 58 U[D[j]] = U[j];//把該元素從其所在列中除去 59 D[U[j]] = D[j]; 60 --S[Col[j]];//該元素所在的列數目減一 61 } 62 } 63 void resume(int c) //恢復c列 64 { 65 for (int i = U[c]; i != c; i = U[i])//枚舉該列元素 66 for (int j = L[i]; j != i; j = L[j])//枚舉該列元素所在的行 67 ++S[Col[U[D[j]] = D[U[j]] = j]];//D[U[j]]=j;U[D[j]]=j;S[Col[j]]++; 68 L[R[c]] = R[L[c]] = c;//c列頭指針左右相連 69 } 70 bool dance(int d) //選取了d行 71 { 72 if (R[0] == 0)//所有覆蓋了 73 { 74 //全覆蓋了以後的操做 75 ansd = d; 76 return 1; 77 } 78 int c = R[0];//表頭結點指向的第一個列 79 for (int i = R[0]; i != 0; i = R[i])//枚舉列頭指針 80 if (S[i]<S[c])//找到列中元素個數最少的 81 c = i; 82 remove(c);//將該列刪去 83 for (int i = D[c]; i != c; i = D[i]) 84 {//枚舉該列的元素 85 ans[d] = Row[i];//記錄該列元素的行 86 for (int j = R[i]; j != i; j = R[j]) 87 remove(Col[j]);//將該列的某個元素的行上的元素所在的列都刪去 88 if (dance(d + 1)) 89 return 1; 90 for (int j = L[i]; j != i; j = L[j]) 91 resume(Col[j]); 92 } 93 resume(c); 94 return 0; 95 } 96 }dlx; 97 98 int main() 99 { 100 int n, m; 101 while (scanf("%d%d", &n, &m) != EOF) 102 { 103 dlx.init(n, m); 104 for (int i = 1; i <= n; i++) 105 {//共n列 106 int k; 107 scanf("%d", &k);//每列中含1的個數 108 while (k--) 109 { 110 int cc; 111 scanf("%d", &cc);//輸入其所在的列 112 dlx.link(i, cc);//連接 113 } 114 } 115 dlx.ansd = -1; 116 if (dlx.dance(0)) 117 { 118 printf("%d", dlx.ansd); 119 for (int i = 0; i<dlx.ansd; i++) 120 printf(" %d", dlx.ans[i]); 121 printf("\n"); 122 } 123 else 124 printf("NO\n"); 125 } 126 return 0; 127 }
二、ZOJ 3209 Treasure Mapui
題意:給出一些矩形,問最少須要多少個矩形能夠把指定的一塊區域覆蓋。spa
思路:把每一個矩形塊當作行,把指定區域分紅1*1的單元格,全部的單元格當作列。指針
1 #include <iostream> 2 #include <stdio.h> 3 #include <string.h> 4 #include<algorithm> 5 //精確覆蓋問題的定義:給定一個由0-1組成的矩陣,是否能找到一個行的集合,使得集合中每一列都剛好包含一個1 6 const int MN = 1005;//最大行數 7 const int MM = 1005;//最大列數 8 const int MNN = 1e5 + 5 + MM; //最大點數 9 10 struct DLX 11 { 12 int n, m, si;//n行數m列數si目前有的節點數 13 //十字鏈表組成部分 14 int U[MNN], D[MNN], L[MNN], R[MNN], Row[MNN], Col[MNN]; 15 //第i個結點的U向上指針D下L左R右,所在位置Row行Col列 16 int H[MN], S[MM]; //記錄行的選擇狀況和列的覆蓋狀況 17 int ansd, ans[MN]; 18 void init(int _n, int _m) //初始化空表 19 { 20 n = _n; 21 m = _m; 22 for (int i = 0; i <= m; i++) //初始化第一橫行(表頭) 23 { 24 S[i] = 0; 25 U[i] = D[i] = i; //目前縱向的鏈是空的 26 L[i] = i - 1; 27 R[i] = i + 1; //橫向的連起來 28 } 29 R[m] = 0; L[0] = m; 30 si = m; //目前用了前0~m個結點 31 for (int i = 1; i <= n; i++) 32 H[i] = -1; 33 } 34 void link(int r, int c) //插入點(r,c) 35 { 36 ++S[Col[++si] = c]; //si++;Col[si]=c;S[c]++; 37 Row[si] = r;//si該結點的行數爲r 38 D[si] = D[c];//向下指向c的下面的第一個結點 39 U[D[c]] = si;//c的下面的第一個結點的上面爲si 40 U[si] = c;//si的上面爲列指針 41 D[c] = si;//列指針指向的第一個該列中的元素設爲si 42 if (H[r]<0)//若是第r行沒有元素 43 H[r] = L[si] = R[si] = si; 44 else 45 { 46 R[si] = R[H[r]];//si的右邊爲行指針所指的右邊第一個元素 47 L[R[H[r]]] = si;//行指針所指的右邊第一個元素的左側爲si 48 L[si] = H[r];//si的左側爲行指針 49 R[H[r]] = si;//行指針的右側爲si 50 } 51 } 52 void remove(int c) //列表中刪掉c列 53 { 54 L[R[c]] = L[c];//表頭操做 //c列頭指針的右邊的元素的左側指向c列頭指針左邊的元素 55 R[L[c]] = R[c];//c列頭指針的左邊的元素的右側指向c列頭指針右邊的元素 56 for (int i = D[c]; i != c; i = D[i])//遍歷該列的全部元素 57 for (int j = R[i]; j != i; j = R[j]) 58 {//對於該列的某個元素所在的行進行遍歷 59 U[D[j]] = U[j];//把該元素從其所在列中除去 60 D[U[j]] = D[j]; 61 --S[Col[j]];//該元素所在的列數目減一 62 } 63 } 64 void resume(int c) //恢復c列 65 { 66 for (int i = U[c]; i != c; i = U[i])//枚舉該列元素 67 for (int j = L[i]; j != i; j = L[j])//枚舉該列元素所在的行 68 ++S[Col[U[D[j]] = D[U[j]] = j]];//D[U[j]]=j;U[D[j]]=j;S[Col[j]]++; 69 L[R[c]] = R[L[c]] = c;//c列頭指針左右相連 70 } 71 bool dance(int d) //選取了d行 72 { 73 if (ansd != -1 && ansd < d)return 0; 74 if (R[0] == 0)//所有覆蓋了 75 { 76 //全覆蓋了以後的操做 77 if(ansd==-1)ansd = d; 78 else if (d < ansd) ansd = d; 79 return 1; 80 } 81 int c = R[0];//表頭結點指向的第一個列 82 for (int i = R[0]; i != 0; i = R[i])//枚舉列頭指針 83 if (S[i]<S[c])//找到列中元素個數最少的 84 c = i; 85 remove(c);//將該列刪去 86 for (int i = D[c]; i != c; i = D[i]) 87 {//枚舉該列的元素 88 ans[d] = Row[i];//記錄該列元素的行 89 for (int j = R[i]; j != i; j = R[j]) 90 remove(Col[j]);//將該列的某個元素的行上的元素所在的列都刪去 91 (dance(d + 1)); 92 for (int j = L[i]; j != i; j = L[j]) 93 resume(Col[j]); 94 } 95 resume(c); 96 return 0; 97 } 98 }dlx; 99 100 int main() 101 { 102 int n, m,p; 103 int t; 104 scanf("%d", &t); 105 while (t--) 106 { 107 scanf("%d%d%d", &n, &m, &p); 108 dlx.init(p, n*m);//將塊當成行,全部的單元格當作列 109 for (int pp = 1; pp <= p; pp++) 110 { 111 int x1, x2, y1, y2; 112 scanf("%d%d%d%d", &x1, &y1, &x2, &y2); 113 for (int i = x1 + 1; i <= x2; i++) 114 { 115 for (int j = y1 + 1; j <= y2; j++) 116 { 117 dlx.link(pp, (i - 1)*m + j); 118 } 119 } 120 } 121 dlx.ansd = -1; 122 dlx.dance(0); 123 printf("%d\n", dlx.ansd); 124 } 125 return 0; 126 }
三、HDU 2295 Radarcode
題意:給出n個城市,給出m個雷達(都是座標),要求在使用不超過k個雷達的狀況下,而且保證覆蓋全部的城市,使得每一個雷達的覆蓋半徑最小。
思路:DLK重複覆蓋模板。
1 #include <iostream> 2 #include <stdio.h> 3 #include <string.h> 4 //重複覆蓋:找到一些行,使得這些行的集合中每列至少有一個1 5 const int MN = 55;//最大行數 6 const int MM = 55;//最大列數 7 const int MNN = MM*MN+MM+MN+10; //最大點數 8 9 struct DLX 10 { 11 int n, m, si;//n行數m列數si目前有的節點數 12 //十字鏈表組成部分 13 int U[MNN], D[MNN], L[MNN], R[MNN], Row[MNN], Col[MNN]; 14 //第i個結點的U向上指針D下L左R右,所在位置Row行Col列 15 int H[MN], S[MM]; //記錄行的選擇狀況和列的覆蓋狀況 16 int ansd, ans[MN]; 17 void init(int _n, int _m) //初始化空表 18 { 19 n = _n; 20 m = _m; 21 for (int i = 0; i <= m; i++) //初始化第一橫行(表頭) 22 { 23 S[i] = 0; 24 U[i] = D[i] = i; //目前縱向的鏈是空的 25 L[i] = i - 1; 26 R[i] = i + 1; //橫向的連起來 27 } 28 R[m] = 0; L[0] = m; 29 si = m; //目前用了前0~m個結點 30 for (int i = 1; i <= n; i++) 31 H[i] = -1; 32 } 33 void link(int r, int c) //插入點(r,c) 34 { 35 ++S[Col[++si] = c]; //si++;Col[si]=c;S[c]++; 36 Row[si] = r;//si該結點的行數爲r 37 D[si] = D[c];//向下指向c的下面的第一個結點 38 U[D[c]] = si;//c的下面的第一個結點的上面爲si 39 U[si] = c;//si的上面爲列指針 40 D[c] = si;//列指針指向的第一個該列中的元素設爲si 41 if (H[r]<0)//若是第r行沒有元素 42 H[r] = L[si] = R[si] = si; 43 else 44 { 45 R[si] = R[H[r]];//si的右邊爲行指針所指的右邊第一個元素 46 L[R[H[r]]] = si;//行指針所指的右邊第一個元素的左側爲si 47 L[si] = H[r];//si的左側爲行指針 48 R[H[r]] = si;//行指針的右側爲si 49 } 50 } 51 void remove(int c) //列表中刪掉c列 52 { 53 //L[R[c]] = L[c];//表頭操做 //c列頭指針的右邊的元素的左側指向c列頭指針左邊的元素 54 //R[L[c]] = R[c];//c列頭指針的左邊的元素的右側指向c列頭指針右邊的元素 55 //for (int i = D[c]; i != c; i = D[i])//遍歷該列的全部元素 56 // for (int j = R[i]; j != i; j = R[j]) 57 // {//對於該列的某個元素所在的行進行遍歷 58 // U[D[j]] = U[j];//把該元素從其所在列中除去 59 // D[U[j]] = D[j]; 60 // --S[Col[j]];//該元素所在的列數目減一 61 // } 62 /*重複覆蓋*//*c爲元素編號,而非列號*//*即刪除該元素所在的列,包括它自身和列頭指針*/ 63 for (int i = D[c]; i != c; i = D[i]) 64 { 65 L[R[i]] = L[i], R[L[i]] = R[i]; 66 } 67 } 68 void resume(int c)//恢復c列 69 { 70 //for (int i = U[c]; i != c; i = U[i])//枚舉該列元素 71 // for (int j = L[i]; j != i; j = L[j])//枚舉該列元素所在的行 72 // ++S[Col[U[D[j]] = D[U[j]] = j]];//D[U[j]]=j;U[D[j]]=j;S[Col[j]]++; 73 //L[R[c]] = R[L[c]] = c;//c列頭指針左右相連 74 /*重複覆蓋*/ 75 for (int i = U[c]; i != c; i = U[i]) 76 { 77 L[R[i]] = R[L[i]] = i; 78 } 79 } 80 int f_check()//精確覆蓋區估算剪枝 81 { 82 /* 83 強剪枝。這個 剪枝利用的思想是A*搜索中的估價函數。即,對於當前的遞歸深度K下的矩陣,估計其最好狀況下(即最少還須要多少步)才能出解。也就是,若是將可以覆蓋當 前列的全部行所有選中,去掉這些行可以覆蓋到的列,將這個操做做爲一層深度。重複此操做直到全部列所有出解的深度是多少。若是當前深度加上這個估價函數返 回值,其和已然不能更優(也就是已經超過當前最優解),則直接返回,沒必要再搜。 84 */ 85 int vis[MNN]; 86 memset(vis, 0, sizeof(vis)); 87 int ret = 0; 88 for (int c = R[0]; c != 0; c = R[c]) vis[c] = true; 89 for (int c = R[0]; c != 0; c = R[c]) 90 if (vis[c]) 91 { 92 ret++; 93 vis[c] = false; 94 for (int i = D[c]; i != c; i = D[i]) 95 for (int j = R[i]; j != i; j = R[j]) 96 vis[Col[j]] = false; 97 } 98 return ret; 99 } 100 bool dance(int d,int limit) //選取了d行,limit爲限制選取的最大行數 101 { 102 /*重複覆蓋 103 一、若是矩陣爲空,獲得結果,返回 104 二、從矩陣中選擇一列,以選取最少元素的列爲優化方式 105 三、刪除該列及其覆蓋的行 106 四、對該列的每一行元素:刪除一行及其覆蓋的列, 107 五、進行下一層搜索,若是成功則返回 108 六、恢復現場,跳至4 109 七、恢復所選擇行 110 */ 111 if (d > limit)return false; 112 if (d + f_check() > limit)return false; 113 if (R[0] == 0)//所有覆蓋了 114 { 115 //全覆蓋了以後的操做 116 ansd = d; 117 return true; 118 } 119 int c = R[0];//表頭結點指向的第一個列 120 for (int i = R[0]; i != 0; i = R[i])//枚舉列頭指針 121 if (S[i]<S[c])//找到列中元素個數最少的 122 c = i; 123 //remove(c);//將該列刪去(精確覆蓋) 124 for (int i = D[c]; i != c; i = D[i]) 125 {//枚舉該列的元素 126 ans[d] = Row[i];//記錄該列元素的行 127 remove(i);//新增(重複覆蓋) 128 for (int j = R[i]; j != i; j = R[j]) 129 remove(j);//remove(Col[j])(精確覆蓋) 130 if (dance(d + 1, limit)) return true; 131 for (int j = L[i]; j != i; j = L[j]) 132 resume(j);//resume(Col[j])(精確覆蓋) 133 resume(i);//新增(重複覆蓋) 134 } 135 //resume(c);(精確覆蓋) 136 return false; 137 } 138 }dlx; 139 struct node 140 { 141 int x; 142 int y; 143 }citys[55],radar[55]; 144 double dis[55][55]; 145 int main() 146 { 147 int n, m,k; 148 int t; 149 scanf("%d", &t); 150 151 while (t--) 152 { 153 double l = 0, r=0,maxx=0,maxy=0; 154 scanf("%d%d%d", &n, &m, &k); 155 for (int i = 1; i <= n; i++) 156 { 157 scanf("%d%d", &citys[i].x, &citys[i].y); 158 if (citys[i].x + citys[i].y > maxx + maxy) 159 { 160 maxx = citys[i].x, maxy = citys[i].y; 161 } 162 } 163 for (int i = 1; i <= m; i++) 164 { 165 scanf("%d%d", &radar[i].x, &radar[i].y); 166 if (radar[i].x + radar[i].y > maxx + maxy) 167 { 168 maxx = radar[i].x, maxy = radar[i].y; 169 } 170 } 171 for (int i = 1; i <= m; i++) 172 { 173 for (int j = 1; j <= n; j++) 174 { 175 dis[i][j] = sqrt((radar[i].x - citys[j].x)*(radar[i].x - citys[j].x) + (radar[i].y - citys[j].y)*(radar[i].y - citys[j].y)); 176 } 177 } 178 r = sqrt(maxx*maxx + maxy*maxy); 179 while (r - l > 1e-7) 180 { 181 double mid = (l + r) / 2; 182 dlx.init(m, n); 183 for (int i = 1; i <= m; i++) 184 { 185 for (int j = 1; j <= n; j++) 186 { 187 if (dis[i][j] <= mid) dlx.link(i, j); 188 } 189 } 190 dlx.ansd = -1; 191 if (dlx.dance(0,k)) r = mid; 192 else l = mid; 193 } 194 printf("%.6lf\n", l); 195 } 196 return 0; 197 }
四、FZU 1686 神龍的難題
題意:有個n*m的矩形,每次能夠把n1*m1小矩形範圍內的敵人消滅,最少的次數。
思路:DLK重複覆蓋模板,枚舉全部的小矩形,設爲行,把全部敵人編號,記爲列。
1 #include<iostream> 2 #include<cstdio> 3 #include<memory.h> 4 #include<algorithm> 5 using namespace std; 6 //重複覆蓋:找到一些行,使得這些行的集合中每列至少有一個1 7 const int MN =250;//最大行數 8 const int MM = 250;//最大列數 9 const int MNN = MM*MN; //最大點數 10 11 struct DLX 12 { 13 int n, m, si;//n行數m列數si目前有的節點數 14 //十字鏈表組成部分 15 int U[MNN], D[MNN], L[MNN], R[MNN], Row[MNN], Col[MNN]; 16 //第i個結點的U向上指針D下L左R右,所在位置Row行Col列 17 int H[MN], S[MM]; //記錄行的選擇狀況和列的覆蓋狀況 18 int ansd, ans[MN]; 19 void init(int _n, int _m) //初始化空表 20 { 21 n = _n; 22 m = _m; 23 for (int i = 0; i <= m; i++) //初始化第一橫行(表頭) 24 { 25 S[i] = 0; 26 U[i] = D[i] = i; //目前縱向的鏈是空的 27 L[i] = i - 1; 28 R[i] = i + 1; //橫向的連起來 29 } 30 R[m] = 0; L[0] = m; 31 si = m; //目前用了前0~m個結點 32 for (int i = 1; i <= n; i++) 33 H[i] = -1; 34 } 35 void link(int r, int c) //插入點(r,c) 36 { 37 ++S[Col[++si] = c]; //si++;Col[si]=c;S[c]++; 38 Row[si] = r;//si該結點的行數爲r 39 D[si] = D[c];//向下指向c的下面的第一個結點 40 U[D[c]] = si;//c的下面的第一個結點的上面爲si 41 U[si] = c;//si的上面爲列指針 42 D[c] = si;//列指針指向的第一個該列中的元素設爲si 43 if (H[r]<0)//若是第r行沒有元素 44 H[r] = L[si] = R[si] = si; 45 else 46 { 47 R[si] = R[H[r]];//si的右邊爲行指針所指的右邊第一個元素 48 L[R[H[r]]] = si;//行指針所指的右邊第一個元素的左側爲si 49 L[si] = H[r];//si的左側爲行指針 50 R[H[r]] = si;//行指針的右側爲si 51 } 52 } 53 void remove(int c) //列表中刪掉c列 54 { 55 //L[R[c]] = L[c];//表頭操做 //c列頭指針的右邊的元素的左側指向c列頭指針左邊的元素 56 //R[L[c]] = R[c];//c列頭指針的左邊的元素的右側指向c列頭指針右邊的元素 57 //for (int i = D[c]; i != c; i = D[i])//遍歷該列的全部元素 58 // for (int j = R[i]; j != i; j = R[j]) 59 // {//對於該列的某個元素所在的行進行遍歷 60 // U[D[j]] = U[j];//把該元素從其所在列中除去 61 // D[U[j]] = D[j]; 62 // --S[Col[j]];//該元素所在的列數目減一 63 // } 64 /*重複覆蓋*//*c爲元素編號,而非列號*//*即刪除該元素所在的列,包括它自身和列頭指針*/ 65 for (int i = D[c]; i != c; i = D[i]) 66 { 67 L[R[i]] = L[i], R[L[i]] = R[i]; 68 } 69 } 70 71 void resume(int c)//恢復c列 72 { 73 //for (int i = U[c]; i != c; i = U[i])//枚舉該列元素 74 // for (int j = L[i]; j != i; j = L[j])//枚舉該列元素所在的行 75 // ++S[Col[U[D[j]] = D[U[j]] = j]];//D[U[j]]=j;U[D[j]]=j;S[Col[j]]++; 76 //L[R[c]] = R[L[c]] = c;//c列頭指針左右相連 77 /*重複覆蓋*/ 78 for (int i = U[c]; i != c; i = U[i]) 79 { 80 L[R[i]] = R[L[i]] = i; 81 } 82 } 83 84 85 bool vis[MNN]; 86 int f_check()//精確覆蓋區估算剪枝 87 { 88 /* 89 強剪枝。這個 剪枝利用的思想是A*搜索中的估價函數。即,對於當前的遞歸深度K下的矩陣,估計其最好狀況下(即最少還須要多少步)才能出解。也就是,若是將可以覆蓋當 前列的全部行所有選中,去掉這些行可以覆蓋到的列,將這個操做做爲一層深度。重複此操做直到全部列所有出解的深度是多少。若是當前深度加上這個估價函數返 回值,其和已然不能更優(也就是已經超過當前最優解),則直接返回,沒必要再搜。 90 */ 91 92 int ret = 0; 93 for (int c = R[0]; c != 0; c = R[c]) vis[c] = true; 94 for (int c = R[0]; c != 0; c = R[c]) 95 if (vis[c]) 96 { 97 ret++; 98 vis[c] = false; 99 for (int i = D[c]; i != c; i = D[i]) 100 for (int j = R[i]; j != i; j = R[j]) 101 vis[Col[j]] = false; 102 } 103 return ret; 104 } 105 void dance(int d) //選取了d行 106 { 107 /*重複覆蓋 108 一、若是矩陣爲空,獲得結果,返回 109 二、從矩陣中選擇一列,以選取最少元素的列爲優化方式 110 三、刪除該列及其覆蓋的行 111 四、對該列的每一行元素:刪除一行及其覆蓋的列, 112 五、進行下一層搜索,若是成功則返回 113 六、恢復現場,跳至4 114 七、恢復所選擇行 115 */ 116 if (d + f_check() >=ansd)return; 117 if (R[0] == 0)//所有覆蓋了 118 { 119 //全覆蓋了以後的操做 120 if(ansd > d) ansd=d; 121 return; 122 } 123 int c = R[0];//表頭結點指向的第一個列 124 for (int i = R[0]; i != 0; i = R[i])//枚舉列頭指針 125 if (S[i]<S[c])//找到列中元素個數最少的 126 c = i; 127 //remove(c);//將該列刪去(精確覆蓋) 128 for (int i = D[c]; i != c; i = D[i]) 129 {//枚舉該列的元素 130 ans[d] = Row[i];//記錄該列元素的行 131 remove(i);//新增(重複覆蓋) 132 for (int j = R[i]; j != i; j = R[j]) 133 remove(j);//remove(Col[j])(精確覆蓋) 134 dance(d + 1); 135 for (int j = L[i]; j != i; j = L[j]) 136 resume(j);//resume(Col[j])(精確覆蓋) 137 resume(i);//新增(重複覆蓋) 138 } 139 //resume(c);(精確覆蓋) 140 } 141 }dlx; 142 int mp[20][20]; 143 int main() 144 { 145 int n, m,n1,m1; 146 while (~scanf("%d%d",&n,&m)) 147 { 148 int cnt = 0; 149 for (int i = 1; i <= n; i++) 150 { 151 for (int j = 1; j <= m; j++) 152 { 153 scanf("%d", &mp[i][j]); 154 if (mp[i][j]) mp[i][j] = ++cnt; 155 } 156 } 157 scanf("%d%d", &n1, &m1); 158 dlx.init(n*m,cnt); 159 cnt = 1; 160 for (int i = 1; i <=n; i++) 161 { 162 for (int j = 1; j <=m; j++) 163 { 164 for (int x = 0; x <n1&&i+x<=n; x++) 165 { 166 for (int z = 0; z < m1&&z+j<=m; z++) 167 { 168 if (mp[i+x][j+z]) dlx.link(cnt, mp[i+x][j+z]); 169 } 170 } 171 cnt++; 172 } 173 } 174 dlx.ansd = 0x3f3f3f3f; 175 dlx.dance(0); 176 printf("%d\n", dlx.ansd); 177 } 178 return 0; 179 }
五、poj 1084 Square Destroyer
題意:給出由火柴構成的網格,如今在已經刪去若干根火柴後,問最少還須要刪去多少火柴,使得網格中的全部正方形被破壞。
思路:DLK重複覆蓋,把每根火柴做爲行,每一個正方形當作列,若是某根火柴能夠構成該正方形的一條邊,則link.
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 #define N 60//55個格子 6 #define M 70//60個火柴 7 #define NN 300//每一個格子4根火柴,算上輔助是5 8 #define inf 0x3f3f3f3f 9 using namespace std; 10 /*60火柴,25格*/ 11 int ans; 12 struct DLX 13 { 14 int U[NN], D[NN], L[NN], R[NN], C[NN]; 15 int H[M], T[N], cnt; 16 inline void init() 17 { 18 cnt = 0; 19 memset(U, 0, sizeof(U)); 20 memset(D, 0, sizeof(D)); 21 memset(L, 0, sizeof(L)); 22 memset(R, 0, sizeof(R)); 23 memset(C, 0, sizeof(C)); 24 memset(H, 0, sizeof(H)); 25 memset(T, 0, sizeof(T)); 26 } 27 inline void newnode(int x, int y) 28 { 29 C[++cnt] = y; T[y]++; 30 31 if (!H[x])H[x] = L[cnt] = R[cnt] = cnt; 32 else L[cnt] = H[x], R[cnt] = R[H[x]]; 33 R[H[x]] = L[R[H[x]]] = cnt, H[x] = cnt; 34 35 U[cnt] = U[y], D[cnt] = y; 36 U[y] = D[U[y]] = cnt; 37 } 38 int id[N][N][5], eid[2][7][7]; 39 bool destroy[N], map[N][M]; 40 inline void build() 41 { 42 init(); 43 int i, j, k, n, m; 44 int nmrx = 0, npon = 0; 45 scanf("%d%d", &n, &m); 46 memset(destroy, 0, sizeof(destroy)); 47 memset(map, 0, sizeof(map)); 48 for (k = 0; k<n; k++)for (i = 1; i + k <= n; i++)for (j = 1; j + k <= n; j++)id[k][i][j] = ++nmrx; 49 for (i = 1; i <= n; i++) 50 { 51 for (j = 1; j <= n; j++)eid[0][i][j] = ++npon; 52 for (j = 1; j <= n + 1; j++)eid[1][i][j] = ++npon; 53 } 54 for (i = 1; i <= n; i++)eid[0][n + 1][i] = ++npon; 55 for (k = 0; k<n; k++) 56 for (i = 1; i + k <= n; i++) 57 for (j = 1; j + k <= n; j++) 58 { 59 int A = id[k][i][j], temp; 60 for (temp = j; temp <= j + k; temp++)map[A][eid[0][i][temp]] = map[A][eid[0][i + k + 1][temp]] = 1; 61 for (temp = i; temp <= i + k; temp++)map[A][eid[1][temp][j]] = map[A][eid[1][temp][j + k + 1]] = 1; 62 } 63 for (i = 1; i <= m; i++) 64 { 65 scanf("%d", &k); 66 for (j = 1; j <= nmrx; j++)if (map[j][k])destroy[j] = 1; 67 } 68 for (i = 1; i <= nmrx; i++)if (!destroy[i]) 69 { 70 U[i] = D[i] = i; 71 L[i] = L[0], R[i] = 0; 72 L[0] = R[L[0]] = i; 73 } 74 cnt = nmrx; 75 for (i = 1; i <= nmrx; i++) 76 if (!destroy[i]) 77 for (j = 1; j <= npon; j++) 78 if (map[i][j]) 79 newnode(j, i); 80 } 81 inline void remove(int x) 82 { 83 int i = x; 84 do 85 { 86 R[L[i]] = R[i]; 87 L[R[i]] = L[i]; 88 i = D[i]; 89 } while (i != x); 90 } 91 inline void resume(int x) 92 { 93 int i = x; 94 do 95 { 96 R[L[i]] = i; 97 L[R[i]] = i; 98 i = U[i]; 99 } while (i != x); 100 } 101 inline void dfs(int x) 102 { 103 if (x >= ans)return; 104 if (!R[0]) 105 { 106 ans = x; 107 return; 108 } 109 int S = R[0], W = T[S], i, j; 110 int del[N], num; 111 for (i = R[S]; i; i = R[i])if (T[i]<W) 112 { 113 W = T[i]; 114 S = i; 115 } 116 remove(S); 117 for (i = D[S]; i != S; i = D[i]) 118 { 119 del[num = 1] = R[i]; 120 for (j = R[R[i]]; j != R[i]; j = R[j])del[++num] = j; 121 for (j = 1; j <= num; j++)remove(del[j]); 122 dfs(x + 1); 123 for (j = num; j; j--)resume(del[j]); 124 } 125 resume(S); 126 return; 127 } 128 }dlx; 129 int main() 130 { 131 int g; 132 scanf("%d", &g); 133 while (g--) 134 { 135 ans = inf; 136 dlx.build(); 137 dlx.dfs(0); 138 printf("%d\n", ans); 139 } 140 return 0; 141 }
六、poj 3074 Sudoku
題意:解9*9的數獨。
思路:主要建圖,每一個不肯定格子至多能夠填9種數字,一共9*9*9行,對於每一個格子,其自身、所在行、所在列、所在塊均存在約束,共9*9*4列。
1 #include <iostream> 2 #include <stdio.h> 3 #include <string.h> 4 //精確覆蓋問題的定義:給定一個由0-1組成的矩陣,是否能找到一個行的集合,使得集合中每一列都剛好包含一個1 5 const int MN = 9*9*9+10;//最大行數,共有9*9個格子,每一個格子能夠放1~9 6 const int MM = 9*9+9*9+9*9+9*9+100;//最大列數 7 //用第(i - 1) * 9 + j列爲1表示i行j列的已經填數。一共佔用81列。 8 //用81 + (i - 1) * 9 + v列表示第i行已經有v這個值。一共佔用81列。 9 //用162 + (j - 1) * 9 + v列表示第j列已經有v這個值。一共佔用81列。 10 //用243 + (3 * ((i - 1) / 3) + (j + 2) / 3 - 1) + v列表示第3*((i - 1) / 3) + (j + 2) / 3宮格已經有v這個值。一共佔用81列。 11 const int MNN = MN*MM; //最大點數 12 13 struct DLX 14 { 15 int n, m, si;//n行數m列數si目前有的節點數 16 //十字鏈表組成部分 17 int U[MNN], D[MNN], L[MNN], R[MNN], Row[MNN], Col[MNN]; 18 //第i個結點的U向上指針D下L左R右,所在位置Row行Col列 19 int H[MN], S[MM]; //記錄行的選擇狀況和列的覆蓋狀況 20 int ansd, ans[MN]; 21 void init(int _n, int _m) //初始化空表 22 { 23 n = _n; 24 m = _m; 25 for (int i = 0; i <= m; i++) //初始化第一橫行(表頭) 26 { 27 S[i] = 0; 28 U[i] = D[i] = i; //目前縱向的鏈是空的 29 L[i] = i - 1; 30 R[i] = i + 1; //橫向的連起來 31 } 32 R[m] = 0; L[0] = m; 33 si = m; //目前用了前0~m個結點 34 for (int i = 1; i <= n; i++) 35 H[i] = -1; 36 } 37 void link(int r, int c) //插入點(r,c) 38 { 39 ++S[Col[++si] = c]; //si++;Col[si]=c;S[c]++; 40 Row[si] = r;//si該結點的行數爲r 41 D[si] = D[c];//向下指向c的下面的第一個結點 42 U[D[c]] = si;//c的下面的第一個結點的上面爲si 43 U[si] = c;//si的上面爲列指針 44 D[c] = si;//列指針指向的第一個該列中的元素設爲si 45 if (H[r]<0)//若是第r行沒有元素 46 H[r] = L[si] = R[si] = si; 47 else 48 { 49 R[si] = R[H[r]];//si的右邊爲行指針所指的右邊第一個元素 50 L[R[H[r]]] = si;//行指針所指的右邊第一個元素的左側爲si 51 L[si] = H[r];//si的左側爲行指針 52 R[H[r]] = si;//行指針的右側爲si 53 } 54 } 55 void remove(int c) //列表中刪掉c列 56 { 57 L[R[c]] = L[c];//表頭操做 //c列頭指針的右邊的元素的左側指向c列頭指針左邊的元素 58 R[L[c]] = R[c];//c列頭指針的左邊的元素的右側指向c列頭指針右邊的元素 59 for (int i = D[c]; i != c; i = D[i])//遍歷該列的全部元素 60 for (int j = R[i]; j != i; j = R[j]) 61 {//對於該列的某個元素所在的行進行遍歷 62 U[D[j]] = U[j];//把該元素從其所在列中除去 63 D[U[j]] = D[j]; 64 --S[Col[j]];//該元素所在的列數目減一 65 } 66 } 67 void resume(int c) //恢復c列 68 { 69 for (int i = U[c]; i != c; i = U[i])//枚舉該列元素 70 for (int j = L[i]; j != i; j = L[j])//枚舉該列元素所在的行 71 ++S[Col[U[D[j]] = D[U[j]] = j]];//D[U[j]]=j;U[D[j]]=j;S[Col[j]]++; 72 L[R[c]] = R[L[c]] = c;//c列頭指針左右相連 73 } 74 bool dance(int d) //選取了d行 75 { 76 if (R[0] == 0)//所有覆蓋了 77 { 78 //全覆蓋了以後的操做 79 ansd = d; 80 return 1; 81 } 82 int c = R[0];//表頭結點指向的第一個列 83 for (int i = R[0]; i != 0; i = R[i])//枚舉列頭指針 84 if (S[i]<S[c])//找到列中元素個數最少的 85 c = i; 86 remove(c);//將該列刪去 87 for (int i = D[c]; i != c; i = D[i]) 88 {//枚舉該列的元素 89 ans[d] = Row[i];//記錄該列元素的行 90 for (int j = R[i]; j != i; j = R[j]) 91 remove(Col[j]);//將該列的某個元素的行上的元素所在的列都刪去 92 if (dance(d + 1)) 93 return 1; 94 for (int j = L[i]; j != i; j = L[j]) 95 resume(Col[j]); 96 } 97 resume(c); 98 return 0; 99 } 100 }dlx; 101 char s[90],path[90]; 102 struct node 103 { 104 int r, c, v; 105 }nds[MN]; 106 int main() 107 { 108 while (~scanf("%s",s)) 109 { 110 if (s[0] == 'e')break; 111 dlx.init(9*9*9,9*9*4); 112 int r=1; 113 for (int i = 1; i <= 9; i++) 114 { 115 for (int j = 1; j <= 9; j++) 116 { 117 if (s[(i - 1) * 9 + j - 1] == '.') 118 { 119 for (int z = 1; z <= 9; z++) 120 { 121 dlx.link(r, (i - 1) * 9 + j); 122 dlx.link(r, 81 + (i - 1) * 9 + z); 123 dlx.link(r, 162 + (j - 1) * 9 + z); 124 dlx.link(r, 243 + (((i - 1) / 3) * 3 + (j + 2) / 3 - 1) * 9 + z); 125 nds[r].r = i, nds[r].c = j, nds[r].v = z; 126 r++; 127 } 128 } 129 else 130 { 131 int z = s[(i - 1) * 9 + j - 1] - '0'; 132 dlx.link(r, (i - 1) * 9 + j); 133 dlx.link(r, 81 + (i - 1) * 9 + z); 134 dlx.link(r, 162 + (j - 1) * 9 + z); 135 dlx.link(r, 243 + (((i - 1) / 3) * 3 + (j + 2) / 3 - 1) * 9 + z); 136 nds[r].r = i, nds[r].c = j, nds[r].v = z; 137 r++; 138 } 139 } 140 } 141 dlx.ansd = -1; 142 dlx.dance(0); 143 int deep = dlx.ansd; 144 for (int i = 0; i < deep; i++) 145 { 146 int posr = dlx.ans[i]; 147 path[(nds[posr].r - 1) * 9 + nds[posr].c - 1] = '0' + nds[posr].v; 148 } 149 path[deep] = '\0'; 150 printf("%s\n", path); 151 } 152 return 0; 153 } 154 /* 155 .2738..1..1...6735.......293.5692.8...........6.1745.364.......9518...7..8..6534. 156 ......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3. 157 end 158 */
七、POJ 3076 Sudoku
題意:解16*16的數獨。
思路:和上題近似,不過維數從9變爲16.
1 #include <iostream> 2 #include <stdio.h> 3 #include <string.h> 4 //精確覆蓋問題的定義:給定一個由0-1組成的矩陣,是否能找到一個行的集合,使得集合中每一列都剛好包含一個1 5 const int MN = 16*16*16 + 10;//最大行數,共有16*16個格子,每一個格子能夠放1~16(A~P) 6 const int MM = 16*16*4 + 100;//最大列數 7 const int MNN = MN*MM;//最大點數 8 9 struct DLX 10 { 11 int n, m, si;//n行數m列數si目前有的節點數 12 //十字鏈表組成部分 13 int U[MNN], D[MNN], L[MNN], R[MNN], Row[MNN], Col[MNN]; 14 //第i個結點的U向上指針D下L左R右,所在位置Row行Col列 15 int H[MN], S[MM]; //記錄行的選擇狀況和列的覆蓋狀況 16 int ansd, ans[MN]; 17 void init(int _n, int _m) //初始化空表 18 { 19 n = _n; 20 m = _m; 21 for (int i = 0; i <= m; i++) //初始化第一橫行(表頭) 22 { 23 S[i] = 0; 24 U[i] = D[i] = i; //目前縱向的鏈是空的 25 L[i] = i - 1; 26 R[i] = i + 1; //橫向的連起來 27 } 28 R[m] = 0; L[0] = m; 29 si = m; //目前用了前0~m個結點 30 for (int i = 1; i <= n; i++) 31 H[i] = -1; 32 } 33 void link(int r, int c) //插入點(r,c) 34 { 35 ++S[Col[++si] = c]; //si++;Col[si]=c;S[c]++; 36 Row[si] = r;//si該結點的行數爲r 37 D[si] = D[c];//向下指向c的下面的第一個結點 38 U[D[c]] = si;//c的下面的第一個結點的上面爲si 39 U[si] = c;//si的上面爲列指針 40 D[c] = si;//列指針指向的第一個該列中的元素設爲si 41 if (H[r]<0)//若是第r行沒有元素 42 H[r] = L[si] = R[si] = si; 43 else 44 { 45 R[si] = R[H[r]];//si的右邊爲行指針所指的右邊第一個元素 46 L[R[H[r]]] = si;//行指針所指的右邊第一個元素的左側爲si 47 L[si] = H[r];//si的左側爲行指針 48 R[H[r]] = si;//行指針的右側爲si 49 } 50 } 51 void remove(int c) //列表中刪掉c列 52 { 53 L[R[c]] = L[c];//表頭操做 //c列頭指針的右邊的元素的左側指向c列頭指針左邊的元素 54 R[L[c]] = R[c];//c列頭指針的左邊的元素的右側指向c列頭指針右邊的元素 55 for (int i = D[c]; i != c; i = D[i])//遍歷該列的全部元素 56 for (int j = R[i]; j != i; j = R[j]) 57 {//對於該列的某個元素所在的行進行遍歷 58 U[D[j]] = U[j];//把該元素從其所在列中除去 59 D[U[j]] = D[j]; 60 --S[Col[j]];//該元素所在的列數目減一 61 } 62 } 63 void resume(int c) //恢復c列 64 { 65 for (int i = U[c]; i != c; i = U[i])//枚舉該列元素 66 for (int j = L[i]; j != i; j = L[j])//枚舉該列元素所在的行 67 ++S[Col[U[D[j]] = D[U[j]] = j]];//D[U[j]]=j;U[D[j]]=j;S[Col[j]]++; 68 L[R[c]] = R[L[c]] = c;//c列頭指針左右相連 69 } 70 bool dance(int d) //選取了d行 71 { 72 if (R[0] == 0)//所有覆蓋了 73 { 74 //全覆蓋了以後的操做 75 ansd = d; 76 return 1; 77 } 78 int c = R[0];//表頭結點指向的第一個列 79 for (int i = R[0]; i != 0; i = R[i])//枚舉列頭指針 80 if (S[i]<S[c])//找到列中元素個數最少的 81 c = i; 82 remove(c);//將該列刪去 83 for (int i = D[c]; i != c; i = D[i]) 84 {//枚舉該列的元素 85 ans[d] = Row[i];//記錄該列元素的行 86 for (int j = R[i]; j != i; j = R[j]) 87 remove(Col[j]);//將該列的某個元素的行上的元素所在的列都刪去 88 if (dance(d + 1)) 89 return 1; 90 for (int j = L[i]; j != i; j = L[j]) 91 resume(Col[j]); 92 } 93 resume(c); 94 return 0; 95 } 96 }dlx; 97 char s[20]; 98 char path[16 * 16 + 10]; 99 struct node 100 { 101 int r, c, v; 102 }nds[MN]; 103 void addlink(int r, int c, int v, int& cnt) 104 { 105 dlx.link(cnt, (r - 1) * 16 + c); 106 dlx.link(cnt, 16*16 + (r - 1) * 16 + v); 107 dlx.link(cnt, 16*16*2 + (c - 1) * 16 + v); 108 dlx.link(cnt, 16*16*3 + (((r-1)/4)*4+(c-1)/4) * 16 + v); 109 nds[cnt].r = r, nds[cnt].c = c, nds[cnt].v = v; 110 cnt++; 111 } 112 int main() 113 { 114 while (~scanf("%s", s)) 115 { 116 117 dlx.init(16*16*16, 16 * 16 * 4); 118 int r = 1; 119 for (int i = 0; i < 16; i++) 120 { 121 if (s[i] == '-') 122 { 123 for (int k = 1; k <= 16; k++) 124 { 125 addlink(1, i + 1, k,r); 126 } 127 } 128 else 129 { 130 int v = s[i] - 'A' + 1; 131 addlink(1, i + 1, v, r); 132 } 133 } 134 for (int i = 2; i <= 16; i++) 135 { 136 scanf("%s", s); 137 for (int j = 1; j <= 16; j++) 138 { 139 if (s[j-1] == '-') 140 { 141 for (int k = 1; k <= 16; k++) 142 { 143 addlink(i, j, k, r); 144 } 145 } 146 else 147 { 148 int v = s[j-1] - 'A' + 1; 149 addlink(i,j, v, r); 150 } 151 } 152 } 153 dlx.ansd = -1; 154 dlx.dance(0); 155 int deep = dlx.ansd; 156 for (int i = 0; i < deep; i++) 157 { 158 int posr = dlx.ans[i]; 159 path[(nds[posr].r - 1) * 16 + nds[posr].c - 1] = 'A' + nds[posr].v-1; 160 } 161 path[deep] = '\0'; 162 for (int i = 0; i < 16; i++) 163 { 164 for (int j = 0; j < 16; j++) printf("%c", path[i * 16 + j]); 165 printf("\n"); 166 } 167 printf("\n"); 168 } 169 return 0; 170 }
八、hdu 4069 Squiggly Sudoku
題意:解9*9的數獨,不過每一塊的形狀不必定是矩形。
思路:先用DFS分塊,而後思路和6相同。
1 #include <iostream> 2 #include <stdio.h> 3 #include <string.h> 4 //精確覆蓋問題的定義:給定一個由0-1組成的矩陣,是否能找到一個行的集合,使得集合中每一列都剛好包含一個1 5 const int MN = 9 * 9 * 9 + 10;//最大行數,共有9*9個格子,每一個格子能夠放1~9 6 const int MM = 9 * 9 + 9 * 9 + 9 * 9 + 9 * 9 + 100;//最大列數 7 //用第(i - 1) * 9 + j列爲1表示i行j列的已經填數。一共佔用81列。 8 //用81 + (i - 1) * 9 + v列表示第i行已經有v這個值。一共佔用81列。 9 //用162 + (j - 1) * 9 + v列表示第j列已經有v這個值。一共佔用81列。 10 //用243 + (3 * ((i - 1) / 3) + (j + 2) / 3 - 1) + v列表示第3*((i - 1) / 3) + (j + 2) / 3宮格已經有v這個值。一共佔用81列。 11 const int MNN = MN*MM; //最大點數 12 int Count;//幾種解決方案 13 bool iscontinue;//當找到兩種及以上解決方案時退出 14 struct DLX 15 { 16 int n, m, si;//n行數m列數si目前有的節點數 17 //十字鏈表組成部分 18 int U[MNN], D[MNN], L[MNN], R[MNN], Row[MNN], Col[MNN]; 19 //第i個結點的U向上指針D下L左R右,所在位置Row行Col列 20 int H[MN], S[MM]; //記錄行的選擇狀況和列的覆蓋狀況 21 int ansd, ans[MN]; 22 int tans[MN];//保存第一個解決方案 23 void init(int _n, int _m) //初始化空表 24 { 25 n = _n; 26 m = _m; 27 for (int i = 0; i <= m; i++) //初始化第一橫行(表頭) 28 { 29 S[i] = 0; 30 U[i] = D[i] = i; //目前縱向的鏈是空的 31 L[i] = i - 1; 32 R[i] = i + 1; //橫向的連起來 33 } 34 R[m] = 0; L[0] = m; 35 si = m; //目前用了前0~m個結點 36 for (int i = 1; i <= n; i++) 37 H[i] = -1; 38 } 39 void link(int r, int c) //插入點(r,c) 40 { 41 ++S[Col[++si] = c]; //si++;Col[si]=c;S[c]++; 42 Row[si] = r;//si該結點的行數爲r 43 D[si] = D[c];//向下指向c的下面的第一個結點 44 U[D[c]] = si;//c的下面的第一個結點的上面爲si 45 U[si] = c;//si的上面爲列指針 46 D[c] = si;//列指針指向的第一個該列中的元素設爲si 47 if (H[r]<0)//若是第r行沒有元素 48 H[r] = L[si] = R[si] = si; 49 else 50 { 51 R[si] = R[H[r]];//si的右邊爲行指針所指的右邊第一個元素 52 L[R[H[r]]] = si;//行指針所指的右邊第一個元素的左側爲si 53 L[si] = H[r];//si的左側爲行指針 54 R[H[r]] = si;//行指針的右側爲si 55 } 56 } 57 void remove(int c) //列表中刪掉c列 58 { 59 L[R[c]] = L[c];//表頭操做 //c列頭指針的右邊的元素的左側指向c列頭指針左邊的元素 60 R[L[c]] = R[c];//c列頭指針的左邊的元素的右側指向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 U[D[j]] = U[j];//把該元素從其所在列中除去 65 D[U[j]] = D[j]; 66 --S[Col[j]];//該元素所在的列數目減一 67 } 68 } 69 void resume(int c) //恢復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 ++S[Col[U[D[j]] = D[U[j]] = j]];//D[U[j]]=j;U[D[j]]=j;S[Col[j]]++; 74 L[R[c]] = R[L[c]] = c;//c列頭指針左右相連 75 } 76 void dance(int d) //選取了d行 77 { 78 if (!iscontinue) return; 79 if (R[0] == 0)//所有覆蓋了 80 { 81 //全覆蓋了以後的操做 82 ansd = d; 83 if (Count == 0) 84 { 85 memcpy(tans, ans, sizeof(ans)); 86 Count++; 87 } 88 else if (Count == 1) iscontinue = false; 89 return; 90 } 91 int c = R[0];//表頭結點指向的第一個列 92 for (int i = R[0]; i != 0; i = R[i])//枚舉列頭指針 93 if (S[i]<S[c])//找到列中元素個數最少的 94 c = i; 95 remove(c);//將該列刪去 96 for (int i = D[c]; i != c; i = D[i]) 97 {//枚舉該列的元素 98 ans[d] = Row[i];//記錄該列元素的行 99 for (int j = R[i]; j != i; j = R[j]) 100 remove(Col[j]);//將該列的某個元素的行上的元素所在的列都刪去 101 dance(d + 1); 102 for (int j = L[i]; j != i; j = L[j]) 103 resume(Col[j]); 104 } 105 resume(c); 106 return ; 107 } 108 }dlx; 109 struct node 110 { 111 int r, c, v; 112 }nds[MN]; 113 int dr[] = { 0,0,1,-1 }; 114 int dc[] = { 1,-1,0,0 }; 115 int mp[10][10];//記錄值 116 bool wall[10][10][2][2];//記錄某個點上下左右是否有牆 117 //[0][0]左,[0][1]右,[1][0]上,[1][1]下 118 int block[10][10];//標記分塊 119 int solution[10][10]; 120 void DFS(int r, int c, int flag) 121 { 122 block[r][c] = flag; 123 if (!wall[r][c][0][0]&&!block[r][c-1]) DFS(r, c - 1, flag); 124 if (!wall[r][c][0][1]&&!block[r][c+1]) DFS(r, c + 1, flag); 125 if (!wall[r][c][1][0]&&!block[r-1][c]) DFS(r - 1, c, flag); 126 if (!wall[r][c][1][1]&&!block[r+1][c]) DFS(r + 1, c, flag); 127 } 128 int main() 129 { 130 int t,Case=1; 131 scanf("%d", &t); 132 while (t--) 133 { 134 int num; 135 memset(mp, 0, sizeof(mp)); 136 memset(wall, 0, sizeof(wall)); 137 memset(block, 0, sizeof(block)); 138 for (int i = 1; i <= 9; i++) 139 { 140 for (int j = 1; j <= 9; j++) 141 { 142 scanf("%d", &num); 143 if (num - 128 >= 0) num -= 128, wall[i][j][0][0] = true; 144 if (num - 64 >= 0) num -= 64, wall[i][j][1][1] = true; 145 if (num - 32 >= 0) num -= 32, wall[i][j][0][1] = true; 146 if (num - 16 >= 0) num -= 16, wall[i][j][1][0] = true; 147 mp[i][j] = num; 148 } 149 } 150 int id = 0; 151 for (int i = 1; i <= 9; i++) 152 { 153 for (int j = 1; j <= 9; j++) 154 { 155 if (!block[i][j]) 156 { 157 DFS(i, j, ++id); 158 } 159 } 160 } 161 dlx.init(9 * 9 * 9, 9 * 9 * 4); 162 int Cnt = 1; 163 for (int i = 1; i <= 9; i++) 164 { 165 for (int j = 1; j <= 9; j++) 166 { 167 if (mp[i][j]==0) 168 { 169 for (int z = 1; z <= 9; z++) 170 { 171 dlx.link(Cnt, (i - 1) * 9 + j); 172 dlx.link(Cnt, 81 + (i - 1) * 9 + z); 173 dlx.link(Cnt, 162 + (j - 1) * 9 + z); 174 dlx.link(Cnt, 243 + (block[i][j]-1) * 9 + z); 175 nds[Cnt].r = i, nds[Cnt].c = j, nds[Cnt].v = z; 176 Cnt++; 177 } 178 } 179 else 180 { 181 int z =mp[i][j]; 182 dlx.link(Cnt, (i - 1) * 9 + j); 183 dlx.link(Cnt, 81 + (i - 1) * 9 + z); 184 dlx.link(Cnt, 162 + (j - 1) * 9 + z); 185 dlx.link(Cnt, 243 + (block[i][j]-1) * 9 + z); 186 nds[Cnt].r = i, nds[Cnt].c = j, nds[Cnt].v = z; 187 Cnt++; 188 } 189 } 190 } 191 dlx.ansd = -1; 192 iscontinue = true; 193 Count = 0; 194 dlx.dance(0); 195 printf("Case %d:\n", Case++); 196 if (Count == 0) 197 { 198 printf("No solution\n"); 199 continue; 200 } 201 else if (!iscontinue) 202 { 203 printf("Multiple Solutions\n"); 204 continue; 205 } 206 int deep = dlx.ansd; 207 for (int i = 0; i < deep; i++) 208 { 209 int posr = dlx.tans[i]; 210 solution[nds[posr].r][nds[posr].c] = nds[posr].v; 211 } 212 for (int i = 1; i <= 9; i++) 213 { 214 for (int j = 1; j <= 9; j++) 215 { 216 printf("%d", solution[i][j]); 217 } 218 printf("\n"); 219 } 220 } 221 return 0; 222 }
九、hdu 3335 Divisibility
題意:給出n個數,每次從中取出一個數後,不能再取它的倍數或其因子。問最多能取多少個數,
思路:DLK求極大重複覆蓋(由於一個數多是若干個數的倍數,因此用重複覆蓋)。當一個數爲另外一個數的因子或倍數時,建邊。
1 #include <iostream> 2 #include <stdio.h> 3 #include <string.h> 4 //重複覆蓋:找到一些行,使得這些行的集合中每列至少有一個1 5 const int MN = 1010;//最大行數 6 const int MM = 1010;//最大列數 7 const int MNN = MM*MN + MM + MN + 10; //最大點數 8 9 struct DLX 10 { 11 int n, m, si;//n行數m列數si目前有的節點數 12 //十字鏈表組成部分 13 int U[MNN], D[MNN], L[MNN], R[MNN], Row[MNN], Col[MNN]; 14 //第i個結點的U向上指針D下L左R右,所在位置Row行Col列 15 int H[MN], S[MM]; //記錄行的選擇狀況和列的覆蓋狀況 16 int ansd, ans[MN]; 17 void init(int _n, int _m) //初始化空表 18 { 19 n = _n; 20 m = _m; 21 for (int i = 0; i <= m; i++) //初始化第一橫行(表頭) 22 { 23 S[i] = 0; 24 U[i] = D[i] = i; //目前縱向的鏈是空的 25 L[i] = i - 1; 26 R[i] = i + 1; //橫向的連起來 27 } 28 R[m] = 0; L[0] = m; 29 si = m; //目前用了前0~m個結點 30 for (int i = 1; i <= n; i++) 31 H[i] = -1; 32 } 33 void link(int r, int c) //插入點(r,c) 34 { 35 ++S[Col[++si] = c]; //si++;Col[si]=c;S[c]++; 36 Row[si] = r;//si該結點的行數爲r 37 D[si] = D[c];//向下指向c的下面的第一個結點 38 U[D[c]] = si;//c的下面的第一個結點的上面爲si 39 U[si] = c;//si的上面爲列指針 40 D[c] = si;//列指針指向的第一個該列中的元素設爲si 41 if (H[r]<0)//若是第r行沒有元素 42 H[r] = L[si] = R[si] = si; 43 else 44 { 45 R[si] = R[H[r]];//si的右邊爲行指針所指的右邊第一個元素 46 L[R[H[r]]] = si;//行指針所指的右邊第一個元素的左側爲si 47 L[si] = H[r];//si的左側爲行指針 48 R[H[r]] = si;//行指針的右側爲si 49 } 50 } 51 void remove(int c) //列表中刪掉c列 52 { 53 //L[R[c]] = L[c];//表頭操做 //c列頭指針的右邊的元素的左側指向c列頭指針左邊的元素 54 //R[L[c]] = R[c];//c列頭指針的左邊的元素的右側指向c列頭指針右邊的元素 55 //for (int i = D[c]; i != c; i = D[i])//遍歷該列的全部元素 56 // for (int j = R[i]; j != i; j = R[j]) 57 // {//對於該列的某個元素所在的行進行遍歷 58 // U[D[j]] = U[j];//把該元素從其所在列中除去 59 // D[U[j]] = D[j]; 60 // --S[Col[j]];//該元素所在的列數目減一 61 // } 62 /*重複覆蓋*//*c爲元素編號,而非列號*//*即刪除該元素所在的列,包括它自身和列頭指針*/ 63 for (int i = D[c]; i != c; i = D[i]) 64 { 65 L[R[i]] = L[i], R[L[i]] = R[i]; 66 } 67 } 68 void resume(int c)//恢復c列 69 { 70 //for (int i = U[c]; i != c; i = U[i])//枚舉該列元素 71 // for (int j = L[i]; j != i; j = L[j])//枚舉該列元素所在的行 72 // ++S[Col[U[D[j]] = D[U[j]] = j]];//D[U[j]]=j;U[D[j]]=j;S[Col[j]]++; 73 //L[R[c]] = R[L[c]] = c;//c列頭指針左右相連 74 /*重複覆蓋*/ 75 for (int i = U[c]; i != c; i = U[i]) 76 { 77 L[R[i]] = R[L[i]] = i; 78 } 79 } 80 int f_check()//精確覆蓋區估算剪枝 81 { 82 /* 83 強剪枝。這個 剪枝利用的思想是A*搜索中的估價函數。即,對於當前的遞歸深度K下的矩陣,估計其最好狀況下(即最少還須要多少步)才能出解。也就是,若是將可以覆蓋當 前列的全部行所有選中,去掉這些行可以覆蓋到的列,將這個操做做爲一層深度。重複此操做直到全部列所有出解的深度是多少。若是當前深度加上這個估價函數返 回值,其和已然不能更優(也就是已經超過當前最優解),則直接返回,沒必要再搜。 84 */ 85 int vis[MNN]; 86 memset(vis, 0, sizeof(vis)); 87 int ret = 0; 88 for (int c = R[0]; c != 0; c = R[c]) vis[c] = true; 89 for (int c = R[0]; c != 0; c = R[c]) 90 if (vis[c]) 91 { 92 ret++; 93 vis[c] = false; 94 for (int i = D[c]; i != c; i = D[i]) 95 for (int j = R[i]; j != i; j = R[j]) 96 vis[Col[j]] = false; 97 } 98 return ret; 99 } 100 void dance(int d) //選取了d行,limit爲限制選取的最大行數 101 { 102 /*重複覆蓋 103 一、若是矩陣爲空,獲得結果,返回 104 二、從矩陣中選擇一列,以選取最少元素的列爲優化方式 105 三、刪除該列及其覆蓋的行 106 四、對該列的每一行元素:刪除一行及其覆蓋的列, 107 五、進行下一層搜索,若是成功則返回 108 六、恢復現場,跳至4 109 七、恢復所選擇行 110 */ 111 //if (ansd!=-1&&d > ansd)return; 112 //if (ansd!=-1&&d + f_check() >ansd)return; 113 if (R[0] == 0)//所有覆蓋了 114 { 115 //全覆蓋了以後的操做 116 if(ansd==-1)ansd = d; 117 else if (d > ansd) ansd = d; 118 } 119 int c = R[0];//表頭結點指向的第一個列 120 for (int i = R[0]; i != 0; i = R[i])//枚舉列頭指針 121 if (S[i]<S[c])//找到列中元素個數最少的 122 c = i; 123 //remove(c);//將該列刪去(精確覆蓋) 124 for (int i = D[c]; i != c; i = D[i]) 125 {//枚舉該列的元素 126 ans[d] = Row[i];//記錄該列元素的行 127 remove(i);//新增(重複覆蓋) 128 for (int j = R[i]; j != i; j = R[j]) 129 remove(j);//remove(Col[j])(精確覆蓋) 130 dance(d + 1); 131 for (int j = L[i]; j != i; j = L[j]) 132 resume(j);//resume(Col[j])(精確覆蓋) 133 resume(i);//新增(重複覆蓋) 134 } 135 //resume(c);(精確覆蓋) 136 return; 137 } 138 }dlx; 139 long long num[1010]; 140 int main() 141 { 142 int n; 143 int t; 144 scanf("%d", &t); 145 146 while (t--) 147 { 148 scanf("%d", &n); 149 for (int i = 1; i <= n; i++) 150 { 151 scanf("%lld", &num[i]); 152 } 153 dlx.init(n, n); 154 for (int i = 1; i <=n; i++) 155 { 156 for (int j = 1; j <= n; j++) if (num[i] % num[j] == 0||num[j]%num[i]==0) dlx.link(i, j); 157 } 158 dlx.ansd = -1; 159 dlx.dance(0); 160 printf("%d\n",dlx.ansd); 161 } 162 return 0; 163 }
拓展:二分圖求最大獨立集(選最多的數使得兩兩之間不能整除)。最大獨立集 = 點數 - 二分圖最大匹配。
1 #include<iostream> 2 #include<queue> 3 #include<memory.h> 4 #include<algorithm> 5 #include<cmath> 6 using namespace std; 7 int n,dis,Ny,Nx; 8 const int maxn = 1010;//最大行數 9 const int maxm = 1010;//最大列數 10 const int maxk = 1010; 11 const int INF = 0x7fffffff; 12 bool mp[maxk][maxk];//1表示該ij能夠匹配 13 int cx[maxk];//記錄x集合中匹配的y元素是哪個 14 int dx[maxk]; 15 int cy[maxk];//記錄y集合中匹配的x元素是哪個 16 int dy[maxk]; 17 int vis[maxk];//標記該頂點是否訪問過 18 bool searchP(void) //BFS 19 { 20 queue <int> Q; 21 dis = INF; 22 memset(dx, -1, sizeof(dx)); 23 memset(dy, -1, sizeof(dy)); 24 for (int i = 1; i <= Nx; i++) 25 if (cx[i] == -1) 26 { 27 Q.push(i); dx[i] = 0; 28 } 29 while (!Q.empty()) 30 { 31 int u = Q.front(); Q.pop(); 32 if (dx[u] > dis) break; //說明該增廣路徑長度大於dis尚未結束,等待下一次BFS在擴充 33 for (int v = 1; v <= Ny; v++) 34 if (mp[u][v] && dy[v] == -1) 35 { //v是未匹配點 36 dy[v] = dx[u] + 1; 37 if (cy[v] == -1) dis = dy[v]; //獲得本次BFS的最大遍歷層次 38 else 39 { 40 dx[cy[v]] = dy[v] + 1; //v是匹配點,繼續延伸 41 Q.push(cy[v]); 42 } 43 } 44 } 45 return dis != INF; 46 } 47 48 bool DFS(int u) 49 { 50 for (int v = 1; v <= Ny; v++) 51 if (!vis[v] && mp[u][v] && dy[v] == dx[u] + 1) 52 { 53 vis[v] = 1; 54 if (cy[v] != -1 && dy[v] == dis) continue; //層次(也就是增廣路徑的長度)大於本次查找的dis,是searchP被break的狀況,也就是還不肯定是不是增廣路徑,只有等再次調用searchP()在判斷。 55 if (cy[v] == -1 || DFS(cy[v])) 56 { //是增廣路徑,更新匹配集 57 cy[v] = u; cx[u] = v; 58 return 1; 59 } 60 } 61 return 0; 62 } 63 64 int MaxMatch() 65 { 66 int res = 0; 67 memset(cx, -1, sizeof(cx)); 68 memset(cy, -1, sizeof(cy)); 69 while (searchP()) 70 { 71 memset(vis, 0, sizeof(vis)); 72 for (int i = 1; i <= Nx; i++) 73 if (cx[i] == -1 && DFS(i)) res++; //查找到一個增廣路徑,匹配數res++ 74 } 75 return res; 76 } 77 long long num[1010]; 78 int main() 79 { 80 int C; 81 scanf("%d", &C); 82 int Case = 1; 83 while (C--) 84 { 85 scanf("%d", &n); 86 for (int i = 1; i <= n; i++) scanf("%lld", &num[i]); 87 sort(num + 1, num + 1 + n); 88 memset(mp, 0, sizeof(mp)); 89 for (int i = 1; i <= n; i++) 90 { 91 for (int j = i+1; j <=n; j++) if (num[j] % num[i] == 0) mp[j][i] = 1; 92 } 93 94 Nx = n, Ny =n; 95 int ans = MaxMatch(); 96 printf("%d\n", n - ans);//無向二分圖:最小路徑覆蓋數 = "拆點"前原圖的頂點數 - 最大匹配數/2 97 } 98 return 0; 99 }