安置囚犯 | ||||||
| ||||||
Description | ||||||
为了降低出现暴动及逃跑事件的风险,两个相同容量的临近监狱的管理层决定重新安排他们的囚犯。他们想用一个监狱里一半的囚犯去交换另一个监狱里一半的囚犯。 然而,从囚犯们犯罪史的存档信息可知某些囚犯成对被关在同一座监狱里时会很危险,这也是现今他们被分开的原因,即对于每对这样的囚犯,一名在第一个监狱服刑,另一名在第二座监狱服刑。管理层认同将那些囚犯保持分开的重要性,但这也使得他们的新安排任务有些棘手。 事实上,他们很快就了解到有时这个互换一半囚犯的意愿是不可能达成的。每当这种情况下,他们不得不满足于交换尽可能接近一半数量的囚犯。 | ||||||
Input | ||||||
输入的第一行是一个正整数n,告诉我们接下来会有多少组测试数据。 每组测试数据以一行两个非负整数m和r开头, 1 < m < 200 是两个监狱各自囚犯的数量,r是那些成对的危险囚犯的对数。 接下来有r行,每行包含一对范围为1到m的整数 xi yi,意思是不能将在第一座监狱服刑的xi与在第二座监狱服刑的yi安排至同一座监狱。 | ||||||
Output | ||||||
对于每组测试数据,输出一行包含一个整数 k <= m/2 ,这是在没把成对的危险囚犯关在同一座监狱的前提下,两个监狱所能交换囚犯的最大数量。 | ||||||
Sample Input | ||||||
3 101 0 3 3 1 2 1 3 1 1 8 12 1 1 1 2 1 3 1 4 2 5 3 5 4 5 5 5 6 6 7 6 8 7 8 8 | ||||||
Sample Output | ||||||
50 0 3 |
思路:
我们用并查集预处理出哪些人可以分到同一联通块中,意味着这两边可以作为一个整体进行交换。
那么并查集维护并且可以O(n)处理出每个背包,设定e【i】.x表示第i个联通块左边监狱需要的花费,e【i】.y表示第i个联通块右边监狱需要的花费,花费同时表示的含义就是可以进行交换的人数的个数。
那么设定Dp【i】【j】表示左边监狱和右边监狱换了i个人,右边监狱和左边监狱换了j个人的情况是否存在。
那么有:
过程维护一下Dp即可。
Ac代码:
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<iostream>
using namespace std;
struct node
{
int x,y;
}e[25000];
int f[2500];
int find(int a)
{
int r=a;
while(f[r]!=r)
r=f[r];
int i=a;
int j;
while(i!=r)
{
j=f[i];
f[i]=r;
i=j;
}
return r;
}
void merge(int a,int b)
{
int A,B;
A=find(a);
B=find(b);
if(A!=B)
f[B]=A;
}
int dp[500][500];
int main()
{
int t;scanf("%d",&t);
while(t--)
{
int n,m;scanf("%d%d",&n,&m);
for(int i=1;i<=n*2;i++)f[i]=i;
for(int i=1;i<=m;i++)
{
int x,y;scanf("%d%d",&x,&y);y+=n;
merge(x,y);
}
int cnt=0;
for(int i=1;i<=n*2;i++)
{
if(f[i]==i)
{
e[cnt].x=e[cnt].y=0;
for(int j=1;j<=n*2;j++)
{
if(find(j)==i)
{
if(j<=n)
{
e[cnt].x++;
}
else e[cnt].y++;
}
}
cnt++;
}
}
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for(int i=0;i<cnt;i++)
{
for(int j=0;j<=n/2;j++)
{
for(int k=0;k<=n/2;k++)
{
if(j>=e[i].x&&k>=e[i].y)
dp[j][k]=max(dp[j][k],dp[j-e[i].x][k-e[i].y]);
}
}
}
int ans=0;
for(int i=1;i<=n;i++)
{
if(dp[i][i]==1)
{
ans=max(ans,i);
ans=min(ans,n/2);
}
}
printf("%d\n",ans);
}
}