首页 > 代码库 > POJ1636 动态规划+并查集

POJ1636 动态规划+并查集

 POJ1636

问题重述:

两个监狱中各有m个囚犯,欲对这两个监狱中的囚犯进行等数量的交换。已知某些囚犯不能关押在同一个监狱,求解可以交换人数的最大值k (k < m/2)。

 

分析:

假设监狱1中的囚犯a与监狱2中的囚犯b不能共存。那么假如对a进行交换,也必须对b进行交换。因此,根据互斥关系建立的连通集两边的成员必须同时进行交换。

 

求解步骤:

1)  根据已知的互斥关系,采用并查集建立连通集,分别记录每个连通集在两个监狱中的成员数目,记为v1, v2。

2)  采用动态规划算法,用布尔变量dp[i][j]表示监狱1中i个囚犯与监狱2中的j个囚犯进行交换的可行性。则有dp[i][j] = dp[i – v1[k]][j – v2[k]]

3)  满足dp[i][i] = 1, i < m/2的i的最大值即所求的解。

  1 //Memory: 580K        Time: 63MS  2 #include <iostream>  3 #include <cstring>  4 #include <cstdio>  5   6 using namespace std;  7   8 const int maxn = 410;  9 int m, r; 10 bool g[maxn][maxn]; 11 int f[maxn]; 12 int nl[maxn]; 13 int nr[maxn]; 14 bool vis[maxn]; 15 int v1[maxn], v2[maxn]; 16 int cnt; 17 bool dp[maxn][maxn]; 18  19 void makeset() 20 { 21     memset(f, 0, sizeof(f)); 22     memset(nl, 0, sizeof(nl)); 23     memset(nr, 0, sizeof(nr)); 24     for (int i = 1; i <= 2 * m; i++) 25         f[i] = i; 26     for (int i = 1; i <= m; i++) { 27         nl[i] = 1; 28         nr[i] = 0; 29     } 30     for (int i = 1 + m; i <= m * 2; i++) { 31         nr[i] = 1; 32         nl[i] = 0; 33     } 34 } 35  36 int find(int a) { 37     if (f[a] == a) return a; 38     f[a] = find(f[a]); 39     return f[a]; 40 } 41  42 void uni(int a, int b) { 43     int sa = find(a); 44     int sb = find(b); 45     if (sa != sb) { 46         f[sa] = sb; 47         nl[sb] += nl[sa]; 48         nr[sb] += nr[sa]; 49     } 50 } 51  52 void init() 53 { 54     makeset(); 55     for (int i = 1; i <= m; i++) { 56         for (int j = m + 1; j <= m * 2; j++) if (g[i][j]) { 57             uni(i, j); 58         } 59     } 60     cnt = 0; 61     for (int i = 1; i <= m * 2; i++) { 62         int s = find(i); 63         if (s == i) { 64             v1[cnt] = nl[s]; 65             v2[cnt++] = nr[s]; 66         } 67     } 68 } 69  70 int main() 71 { 72     int cas; 73     cin >> cas; 74     while (cas--) { 75         memset(g, 0, sizeof(g)); 76         scanf("%d%d", &m, &r); 77         int a, b; 78         for (int i = 0; i < r; i++) { 79             scanf("%d%d", &a, &b); 80             g[a][b + m] = 1; 81         } 82         init(); 83  84         memset(dp, 0, sizeof(dp)); 85         dp[0][0] = 1; 86         for (int i = 0; i < cnt; i++) { 87             for (int j = m/2; j >= 0; j--)  ////此处必须进行倒序循环:每次循环的dp都由上一轮循环后序号较小的dp确定,倒序循环避免提前更新序号较小的dp 88                 for (int k = m/2; k >= 0; k--) {  //同上 89                     if (dp[j][k] && j + v1[i] <= m/2 && k + v2[i] <= m/2) 90                         dp[j + v1[i]][k + v2[i]] = 1; 91                 } 92         } 93          94         for (int i = m / 2; i >= 0; i--) { 95             if (dp[i][i]) { 96                 cout << i <<endl; 97                 break; 98             } 99         }100     }101     return 0;102 }