问题重述:
两个监狱中各有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 }
POJ1636 动态规划+并查集
时间: 2024-10-11 10:30:54