poj 3740 Easy Finding 二进制压缩枚举dfs 与 DLX模板详细解析

题目链接: 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 }

 

posted @ 2016-08-18 16:27  hxer  阅读(334)  评论(0编辑  收藏  举报