Hrbust 1537 安置囚犯【并查集+二维费用背包】

87 篇文章 0 订阅
22 篇文章 1 订阅

安置囚犯
Time Limit: 1000 MSMemory Limit: 65535 K
Total Submit: 7(5 users)Total Accepted: 4(4 users)Rating: Special Judge: No
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);
    }
}




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值