征兵
【题目描述】:
一个国王,他拥有一个国家。最近他因为国库里钱太多了,闲着蛋疼要征集一只部队要保卫国家。他选定了N个女兵和M个男兵,但事实上每征集一个兵他就要花10000RMB,即使国库里钱再多也伤不起啊。他发现,某男兵和某女兵之间有某种关系(往正常方面想,一共R种关系),这种关系可以使KING少花一些钱就可以征集到兵,不过国王也知道,在征兵的时候,每一个兵只能使用一种关系来少花钱。这时国王向你求助,问他最少要花多少的钱。
【输入描述】:
第一行:T,一共T组数据。
接下来T组数据,
第一行包括N,M,R
接下来的R行 包括Xi,Yi,Vi 表示如果招了第Xi个女兵,再招第Yi个男兵能省Vi元(同样表示如果招了第Yi个男兵,再招第Xi个女兵能也省Vi元)
【输出描述】:
共T行,表示每组数据的最终花费是多少(因为国库里的钱只有2^31-1,所以保证最终花费在maxlongint范围内)
【样例输入】:
2
5 5 8
4 3 6831
1 3 4583
0 0 6592
0 1 3063
3 3 4975
1 3 2049
4 2 2104
2 2 781
5 5 10
2 4 9820
3 2 6236
3 1 8864
2 4 8326
2 0 5156
2 0 1463
4 1 2439
0 4 4373
3 4 8889
2 4 3133
【样例输出】:
71071
54223
【时间限制、数据范围及描述】:
时间:1s 空间:128M
T<=5 ,m,n<=10000,r<=50000,Xi<=m,Yi<=n,Vi<=10000,结果<=2^31-1
吐槽一下,一个士兵只能发生一种关系我一直以为两个士兵只能用一种省钱方式。。。
其实这题意思就是两个士兵有某种关系,就像两点之间有一条的路一样,每个点要找一个划算的点连起来,要在所有操作找一种总和最大的方案。那个因为上次最小生成树不太懂并查集是什么,所以自己用贪心做的,后来知道那是prim算法。这题最大生成树,我想了一下应该也是可以用贪心做的,但为了学习并查集我就不那样写了。(最小生成树我写的贪心也可以参考一下点击打开链接)
这里首先处理一下,把男兵放到女兵数组后面去,形成a[1]~a[m+n]。然后给所有操作排个序(其实这也是贪心,prim算法
以点为中心,而kruska算法是以边为中心的)。之后从钱最多的开始,每次看两个端点是否在一个集合,如果不在说明连起来是很完美的。如果在的话,那么你就无法选择了,因为不能连成一个环,这样等于有一个士兵用了两种省钱方式。
所以我们需要用并查集,可以理解为是用一个符号代表这个集合,初始值就是自身,合并后就对其进行修改(相当于收了个小弟),
需要查询时就用一个递归上层找,直到找到集合最初的老大。如果结果相同就是在一个集合,否则就可以进行修改并累加ans.
int find(int x)
{return x==f[x]?x:f[x]=find(f[x]);} f[]就是记录他们的所在集合的,这样的递归就可以搜索的该端点所在集合了
for(int i=1;i<=r;i++)
{
int p=find(a[i].x),q=find(a[i].y);
if(p!=q)
{f[p]=q,ans+=a[i].v;}
}
这就是中心程序了,找到两个端点所在集合,不在一起的话就给他们连起来,怎么连呢,就是用f[]数组修改,就好比养了个小弟了。
具体还是需要看程序的
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#define inf 0x7fffffff
#define ll long long
using namespace std;
ll T,n,m,r;
int f[20005];
struct node{
int x,y,v;
}a[50005];
bool cmp(node a,node b)
{return a.v>b.v;}
int find(int x)
{return x==f[x]?x:f[x]=find(f[x]);}//查询
int main()
{
//freopen("test1.in","r",stdin);
//freopen("test1.out","w",stdout);
cin>>T;
while(T--)
{
ll ans=0;
cin>>n>>m>>r;
for(int i=1;i<=n+m;i++)f[i]=i;//并查集的初始化
for(int i=1;i<=r;i++)
{
cin>>a[i].x>>a[i].y>>a[i].v;
a[i].x++;
a[i].y++;
a[i].y+=n;
} //输入后整理
sort(a+1,a+r+1,cmp);//按从大到小排序
for(int i=1;i<=r;i++)
{
int p=find(a[i].x),q=find(a[i].y);//p,q是所在集合
if(p!=q) //不在一个集合
{f[p]=q;//进行修改,合并啦
ans+=a[i].v;//加上这次省的钱
}
}
printf("%d\n",(n+m)*10000-ans);
}
return 0;
}