poj3723--Conscription(最大权森林问题)

题意:招募n女,m男,其中r对有亲密关系,招募每个人花费为10000-(已经招募的人中和自己亲密度的最大值),求招募所有人的最小总花费。
思路:乍一看以为是二分图,吓得想了一下觉得还是二分图,但其实这个思路转化一下就是最大生成树模板题;可以看作先把所有人招募进来之后减掉亲密关系的优惠。亲密关系的最大优惠即为最大生成树的结果的权值。

错误思路:我的第一个思路是用最小生成树加上一个sum数组记录根节点的子节点总数,但是这样会出现两个问题。
1.sum数组记录的并不是总节点数,因为图未完全联通的时候可能是森林的状态。
2.最小生成树思路不对:我是将每个有亲密关系的cost都变为10000-亲密关系在去求最小生成树的总权值,但是这样并不是正确的答案:假设第一个人进来后第二个和她有亲密关系的人在进来,用这种想法求出的招募两个人的cost值为10000-(亲密值),但其实正确的值大于此,应该是第一个人付10000,第二个人才付优惠后的值。故采用逆向思维较好,即先招募所有,把总花费减掉亲密关系加入的最大生成树的权值。

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n,m,r;//n女,m男,r对亲密关系;
int f[20050],sum[20050];
int ans;
struct node
{
    int id1;
    int id2;
    int dis;//这里的dis为10000-输入值;
}cost[50050];

bool cmp(node a,node b)
{
    return a.dis>b.dis;
}

inline void ini()
{
    for(int i=0;i<=n+m-1;i++)//注意节点数为n+m个;
    {
        f[i]=i;
        sum[i]=1;
    }
    ans=0;
}

int find(int x)
{
    while(x!=f[x])
    {
        f[x]=f[f[x]];
        x=f[x];
    }
    return x;
}

int main()
{
    int i,j;
    int t;
    int a,b,c;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d%d",&n,&m,&r);
        ini();
        for(i=1;i<=r;i++)
        {
            scanf("%d%d%d",&a,&b,&c);
            cost[i].id1=a;
            cost[i].id2=b+n;//注意:女生节点为0~n-1,男生节点为n~(n+m-1);
            cost[i].dis=c;
        }
        sort(cost+1,cost+1+r,cmp);
        for(i=1;i<=r;i++)//节点联通;
        {
            int fx,fy;
            fx=find(cost[i].id1);
            fy=find(cost[i].id2);
            if(fx!=fy)
            {
                if(sum[fx]>sum[fy])
                {
                    f[fy]=fx;
                    sum[fx]+=sum[fy];
                }
                else
                {
                    f[fx]=fy;
                    sum[fy]+=sum[fx];
                }
                ans+=cost[i].dis;
            }
        }
        printf("%d\n",(n+m)*10000-ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值