题目链接: http://poj.org/problem?id=3740
题意: 是否从0,1矩阵中选出若干行,使得新的矩阵每一列有且仅有一个1?
原矩阵N*M $ 1<= N <= 16 $ , $ 1 <= M <= 300$
解法1:由于行数不多,二进制枚举选出的行数,时间复杂度为O((1<<16)*K), 其中K即为判断选出的行数是否存在相同的列中有重复的1;
优化:将1状压即可,这样300的列值,压缩在int的32位中,使得列数“好像”小于10了;这样每次只需要 一个数 进行与运算,即可判断32个列是否有重复的1;使得K <= 10;
并且在选中一列和删除一列时, 两次 xor操作就可复原~~
47ms ~~
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 6 const int bit = 32; 7 const int L = 10; 8 int n, m, x[20][L], g[L], w[L]; 9 bool check(int *x, int *w) 10 { 11 for(int i = 0; i < L; i++){ 12 if(x[i] & w[i]) return true; 13 } 14 return false; 15 } 16 bool dfs(int p) 17 { 18 int flag = 1; 19 for(int i = 0; i < L && flag; i++) if(g[i] != w[i]) flag = 0; 20 if(flag) return true; 21 if(p == n) return false; 22 if(check(x[p], w)) return dfs(p + 1); // 判断是否当前的列与之前选中的列有重复的1 23 24 for(int i = 0; i < L; i++) w[i] ^= x[p][i]; 25 if(dfs(p+1)) return true; 26 for(int i = 0; i < L; i++) w[i] ^= x[p][i]; // 不选当前的列 27 return dfs(p+1); 28 } 29 int main() 30 { 31 char a; 32 while( scanf("%d%d", &n, &m) == 2){ 33 memset(x, 0, sizeof(x)); 34 for(int i = 0; i < n; i++){ 35 for(int j = 0; j < m; j++){ 36 a = getchar(); 37 while (!isdigit(a)) a = getchar(); 38 if (a == '1') x[i][j/bit] |= 1 << j%bit; 39 } 40 } 41 memset(w, 0, sizeof(w)); 42 memset(g, 0, sizeof(g)); 43 for(int i = 0; i < m; i++) g[i/bit] |= 1 << i%bit; 44 45 if(dfs(0)) puts("Yes, I found it"); 46 else puts("It is impossible"); 47 } 48 }
解法2: DLX模板题,初学DLX,下面写下理解:
在DLX中搞清楚几个函数的含义,以及删除和恢复列操作的含义基本上理解了DLX 的模板了;
C[i] 表示每个有效节点所在的列, S[i] 表示每一列有效节点的个数,H[i] 表示每一列最右边有效节点的的标号;
L[i], R[i], U[i], D[i] 分别表示有效节点 i 上下左右节点的标号;
其中U[c] (c为列的标号) 表示列c中最下面的有效节点的标号, D[c] (c为列的标号)表示列c中最上面的有效节点的标号;
并且在删除和恢复一列时,循环遍历的是该列中的每一个节点; 这和选中一列,之后删除/恢复 行不同, 删除/恢复 行并没有包括起点(起点在列中操作了);
具体的DLX思路: 选出S中最小的列c, 删除列c中所有的有效节点所在的行,之后枚举选出的是哪行;再删除该行所有有效节点所在的列;递归求解即可;
63ms ... 进竟然没有压缩枚举快,,写挫了
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 #define inf 0x3f3f3f3f 6 const int maxn = 5000; 7 int tot, head, n, m; 8 int C[maxn], S[maxn], H[maxn]; 9 int U[maxn], D[maxn], L[maxn], R[maxn]; 10 inline void add_link(int i, int j) 11 { 12 C[++tot] = j; 13 S[j]++; 14 15 D[tot] = j; 16 U[tot] = U[j]; 17 D[U[tot]] = tot; 18 U[D[tot]] = tot;//U[j]表示第j列最下面节点标号; 19 20 if(H[i]) L[tot] = H[i], R[tot] = R[H[i]]; // H[i]是第i行最右边的节点标号 21 else R[tot] = L[tot] = tot; 22 23 R[L[tot]] = tot; 24 L[R[tot]] = tot; 25 H[i] = tot; 26 } 27 28 void Remove(int c) 29 { 30 R[L[c]] = R[c]; L[R[c]] = L[c]; 31 for(int i = D[c]; i != c ; i = D[i]){ 32 for(int j = R[i]; j != i; j = R[j]){ 33 U[D[j]] = U[j]; 34 D[U[j]] = D[j]; 35 S[C[j]]--; 36 } 37 } 38 } 39 40 void Resume(int c) 41 { 42 for(int i = U[c]; i != c; i = U[i]){ 43 for(int j = L[i]; j != i; j = L[j]){ 44 U[D[j]] = j; 45 D[U[j]] = j; 46 S[C[j]]++; 47 } 48 } 49 R[L[c]] = c; L[R[c]] = c; 50 } 51 bool DLX() 52 { 53 if(R[head] == head) return true; 54 int c, mn = inf; 55 for(int i = R[head]; i != head; i = R[i]) if(mn > S[i]) c = i, mn = S[i]; 56 57 Remove(c); 58 for(int i = D[c]; i != c; i = D[i]){ 59 for(int j = R[i]; j != i; j = R[j]) Remove(C[j]); 60 61 if(DLX()) return true; 62 for(int j = L[i]; j != i; j = L[j]) Resume(C[j]); 63 } 64 Resume(c); 65 66 return false; 67 } 68 int main() 69 { 70 while( scanf("%d%d", &n, &m) == 2){ 71 tot = m; head = 0; 72 for(int i = 0; i <= m; i++){ 73 C[i] = U[i] = D[i] = i; 74 L[i+1] = i; 75 R[i] = i+1; 76 S[i] = 0; 77 } 78 L[0] = m, R[m] = 0; 79 80 char x; 81 for(int i = 1; i <= n; i++){ 82 H[i] = 0; 83 for(int j = 1; j <= m; j++){ 84 x = getchar(); 85 while (!isdigit(x)) x = getchar(); 86 if (x == '1') add_link(i, j); 87 } 88 } 89 if(DLX()) puts("Yes, I found it"); 90 else puts("It is impossible"); 91 } 92 }