题意:招募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;
}