P4809 题解
提供一个新的思路。
不难发现,每个星球内部的最小生成树的结构是一样的。于是我们只需要跑一遍 \(\rm{Kruskal}\) 即可得到所有星球内部的生成树,顺便记录下生成树所用边的边权。
此时,星球之间还是未连通的。于是我们再在星球之间跑一边 \(\rm{Kruskal}\) ,即可连通所有星球。
不过,如果你选择的星球内部的边的边权很大,答案很可能不是最优的,因为你可以用其他星球间的边替换这条比较劣的边。具体来说,就是在给星球间连边时,在生成树内部的边中 \(\rm{lower\_bound}\) 找到比当前边列劣的内部边,然后计算新边产生的贡献即可。
结合代码的注释更好理解。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=4e5+7;
int n,m,p,q;
int fa[N];
int cnt;
ll ans=0,tot1=0,tot2=0;//两个tot记录的是总边权和
struct Edge{
int x,y;
int z;
friend bool operator<(const Edge &a,const Edge &b){
return a.z<b.z;
}
}e[1<<24];
ll stk[N],sum[N],top;
int find(int x){
if(fa[x]==x) return x;
return fa[x]=find(fa[x]);
}
void merge(int x,int y){
int fx=find(x),fy=find(y);
fa[fy]=fx;
return;
}
int main(){
scanf("%d%d%d%d",&n,&m,&p,&q);
for(int i=1;i<=m;i++) fa[i]=i;
for(int i=1;i<=p;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
tot1+=z;//重边和自环不需要考虑,克鲁斯卡尔的过程会帮我们过滤掉
e[i]={x,y,z};
}
sort(e+1,e+p+1);
for(int i=1;i<=p;i++){//星球内部
int x=e[i].x,y=e[i].y,z=e[i].z;
if(find(x)==find(y)) continue;
merge(x,y);
ans+=z,stk[++top]=z;//星球内部的边记录下来
}
ans*=n,tot1*=n;
for(int i=1;i<=n;i++) fa[i]=i;//记得再初始化一遍
for(int i=1;i<=q;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
tot2+=z;
e[i]={x,y,z};
}
tot2*=m;
sort(e+1,e+q+1);
sort(stk+1,stk+top+1);
for(int i=1;i<=top;i++)
sum[i]=sum[i-1]+stk[i];
for(int i=1;i<=q;i++){//星球间
int x=e[i].x,y=e[i].y,z=e[i].z;
if(find(x)==find(y)) continue;
merge(x,y);
ans+=z;
int pos=lower_bound(stk+1,stk+top+1,z)-stk;
ans+=z*(top-pos+1);//使用星球间的边替换掉星球内部不优的边
ans-=(sum[top]-sum[pos-1]);
}
printf("%lld\n",tot2+tot1-ans);
return 0;
}