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 }

POJ1636 动态规划+并查集

时间: 2024-10-11 10:30:54

POJ1636 动态规划+并查集的相关文章

POJ 1417 True Liars 并查集+背包

题目链接:http://poj.org/problem?id=1417 解题思路:比较容易想到的是并查集,然后把第三组数据测试一下之后发现这并不是简单的并查集,而是需要合并之后然后判断的.并且鉴于题目要求输出数据,因此还要记录数据,可以说是非常有意思的题目. 首先,如果a b yes,那么a与b一定都是圣人或者恶人:反之,如果a b no,那么两者一个圣人,一个恶人.因此可以将所有元素分为若干个集合,每个集合中放两个小集合,表示两种不一样的人,当然并不知道到底哪个里面是圣人,恶人. 另一个比较棘

CodeForces 745C Hongcow Builds A Nation 并查集

题意: 给了你n个城市 m条边 k个政府 每个政府管辖的区域内不能和其他政府的区域有相连 即政府之间不存在路径 问你在维护这种关系的同时 最多再加多少条边 思路: 先找出来每个联通块 再找出来没有归属的孤立的点 把他们都放到最大的联通块里 然后每个联通块之间的点两两连边是n*(n-1)/2条边 最后算出来的ans-m就好了 (看别人的博客学了一个max_element 1 #include<bits/stdc++.h> 2 #define cl(a,b) memset(a,b,sizeof(a

并查集(个人模版)

并查集: 1 int find(int a) 2 { 3 int r=a; 4 while(f[r]!=r) 5 r=f[r]; 6 int i=a; 7 int j; 8 while(i!=r) 9 { 10 j=f[i]; 11 f[i]=r; 12 i=j; 13 } 14 return r; 15 } 16 int merge(int a,int b) 17 { 18 int A,B; 19 A=find(a); 20 B=find(b); 21 if(A!=B) 22 { 23 f[B

并查集应用

题目描述: One way that the police finds the head of a gang is to check people's phone calls. If there is a phone call between A and B, we say that A and B is related. The weight of a relation is defined to be the total time length of all the phone calls

【bzoj3674】 可持久化并查集加强版

http://www.lydsy.com/JudgeOnline/problem.php?id=3674 (题目链接) 题意 维护并查集3个操作:合并:回到完成第k个操作后的状态:查询. Solution 其实就是用主席树的叶子节点维护并查集的可持久化数组fa[]. 细节 终于认识到了按秩合并的强大,单纯写个路径压缩Re飞,写了路径压缩+按秩合并比单纯的按秩合并每快多少→_→ 代码 // bzoj3674 #include<algorithm> #include<iostream>

BZOJ1015[JSOI2008]星球大战starwar[并查集]

1015: [JSOI2008]星球大战starwar Time Limit: 3 Sec  Memory Limit: 162 MBSubmit: 5253  Solved: 2395[Submit][Status][Discuss] Description 很久以前,在一个遥远的星系,一个黑暗的帝国靠着它的超级武器统治者整个星系.某一天,凭着一个偶然的机遇,一支反抗军摧毁了帝国的超级武器,并攻下了星系中几乎所有的星球.这些星球通过特殊的以太隧道互相直接或间接地连接. 但好景不长,很快帝国又重

HDU 5606 tree 并查集

tree 把每条边权是1的边断开,发现每个点离他最近的点个数就是他所在的连通块大小. 开一个并查集,每次读到边权是0的边就合并.最后Ans?i??=size[findset(i)],size表示每个并查集根的size Ans_i=size[findset(i)],sizeAns?i??=size[findset(i)],size表示每个并查集根的sizesize. #include<cstdio> #include<cstring> #include<algorithm>

HDU 5441 离线处理 + 并查集

题意:给n个节点m条带权值边的无向图.然后q个问题,每次询问点对的数目,点对需要满足的条件是:1)连通:2)其路径的最大权值不能超过询问值. 分析:如果没次询问一次,dfs一次,很可能超时,因此可以用并查集.离线处理,把边按权值排序,把问题按大小排序.然后离线的过程就是不断向图中加边的过程. 比如样例如下: 然后离线处理,排完序后将会是一条一条的加边:问题也排了序,因此是个累加过程... 1 #include <cstdio> 2 #include <iostream> 3 #in

poj1988 Cube Stacking(并查集

题目地址:http://poj.org/problem?id=1988 题意:共n个数,p个操作.输入p.有两个操作M和C.M x y表示把x所在的栈放到y所在的栈上(比如M 2 6:[2 4]放到[1 6]上为[2 4 1 6]),C x为输出x下面有几个数. 思路:并查集每个集合以栈最下面的数为根,维护两个数组num[x]表示x所在集合节点总数,count[x]表示x下方节点个数.每次查找压缩路径的时候更新count(换父节点的时候每轮都把父节点的count加给儿子,就可以一直更新到x所在栈