原题链接:https://vjudge.net/problem/Gym-101137K
大致题意:给出n个点,m条边的图,要占领点i至少需要聚集ai个士兵到点i,空降1个士兵到点i的代价是bi,在一个点上可以的空降的士兵数量没有上限,只要在一条边的两个节点上聚集ci个士兵就可以占领这条边,占领某一条边以后可以无代价经过这条边,且在占领边或点的过程中士兵不会有牺牲,士兵在占领一个点以后可以经由被占领的边到去占领另一个点。
解法:先按照边权排序一遍,在用并查集维护连通块,同时维护每个连通块内部的空降士兵的最小单价 和 占领连通块内部点和边所需士兵数量的最大值 以及 占领连通块所有点的代价。在枚举到某一条边的时候,可以先统计同时占领边所在2个连通块的代价和,再把整体的最小单价和最大士兵数量更新,可能会有人觉得这样子岂不是表示这条边一定会被选中吗?(我当时就是这么想的,结果纠结了好久,害的我们队罚时被踩,浪费了至少2个小时的时间TAT,我的锅)
的确,这条边一定会被选中!!!因为如果在后面的合并连通块的过程中发现如果把这条边打通使得原有的两个连通块联结作为一个连通块会更优,那么由于边权是从小到大枚举的,要联结后面的边就一定可以使得这个连通块的士兵数量不小于这条边的限制,从而控制这条边。
再总结一下吧,kruskal算法的应用要求之一就是要求边权较小的一定会在边权大的之前被选中而不会使得结果变得更差,这题就是十分符合这一特征的。
代码:
#include <bits/stdc++.h>
using namespace std;
inline void read(int &x){
char ch;
bool flag=false;
for (ch=getchar();!isdigit(ch);ch=getchar())if (ch=='-') flag=true;
for (x=0;isdigit(ch);x=x*10+ch-'0',ch=getchar());
x=flag?-x:x;
}
inline void read(long long &x){
char ch;
bool flag=false;
for (ch=getchar();!isdigit(ch);ch=getchar())if (ch=='-') flag=true;
for (x=0;isdigit(ch);x=x*10+ch-'0',ch=getchar());
x=flag?-x:x;
}
const int maxn=310000;
const int maxm=710000;
struct Edge{
long long st,ed;
long long v;
}edge[maxm];
long long fa[maxn];
long long a[maxn],b[maxn];
long long Maxv[maxn],Minb[maxn];
long long n,m;
long long sum[maxn];
long long get_fa(long long x){
if (fa[x]==x)
return x;
return fa[x]=get_fa( fa[x] );
}
bool cmp(Edge A,Edge B){
return A.v<B.v;
}
long long ans;
int main(){
read(n); read(m);
for (int i=1;i<=n;i++)
{
read(a[i]);
read(b[i]);
sum[i]=a[i]*b[i];
}
for (int i=1;i<=m;i++)
{
read(edge[i].st);
read(edge[i].ed);
read(edge[i].v);
}
sort(edge+1,edge+1+m,cmp);
for (int i=1;i<=n;i++)
{
fa[i]=i;
Maxv[i]=a[i];
Minb[i]=b[i];
}
for (int i=1;i<=m;i++)
{
int x=edge[i].st,y=edge[i].ed;
x=get_fa(x); y=get_fa(y);
if (x==y)
continue;
fa[ y ]=x;
Maxv[ x ]=max( edge[ i ].v , max( Maxv[ x ] , Maxv[ y ] ) );
Minb[ x ]=min( Minb[x] ,Minb[y] );
sum[ x ] = min ( sum[x] + sum[y] , Maxv[x]*Minb[x] );
}
ans=0;
for (int i=1;i<=n;i++)
if (fa[i]==i)
ans=ans+sum[i];
cout<<ans<<endl;
return 0;
}