zoukankan      html  css  js  c++  java
  • 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 }
  • 相关阅读:
    mac 终端常见指令
    git常见指令
    iOS8的autolayout和size class
    UIWindow详解
    操作系统Unix、Windows、Mac OS、Linux的故事
    iOS引用当前显示的UIAlertView
    Unexpected CFBundleExecutable Key
    《CODE》讲了什么?
    exit和return的区别
    php 登录注册api接口代码
  • 原文地址:https://www.cnblogs.com/hxer/p/5784406.html
Copyright © 2011-2022 走看看