P4809 题解

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;
}
posted @ 2021-09-16 08:00  actypedef  阅读(65)  评论(0编辑  收藏  举报