洛谷P5633最小度限制生成树

先不要看点s,对与点s无关的边用kruskal算法构建最小生成森林,将加入最小生成森林的边的权值累加入ans中,同时做这些事情:
在使用kruskal算法构建最小生成森林前,预处理出每个点 i 连接到s的边的边权最小值val[i],如果点 i 与s没有直接相连的边,则val[i]=inf ,取最小值是因为点 i 连接到s的边可能不止一条

在构建最小生成森林时,一般的kruskal直接将点u所属集合x并入点v所属集合y,现在我们为了让最终的最小生成森林里的连通块与s连接的边权最小,每次合并集合的时候,让val[y]<=val[x],如果不满足,就交换x和y让这个条件满足

满足以上条件时,在将集合x并入集合y的时候,将一个代价差:val[x]-w(w是边u-v的边权)放入数组b中,这个代价差表示,不把集合x(连通块x)连向集合y(连通块y),而是将集合x连向s点,产生的代价变动,前提是val[x]不是inf,也就是连通块x能连向s

然后,统计最小生成森林里的连通块数量,记成tot,如果tot>k,说明无论如何加上点s后,与s相连的边的数量会超过k,所以抛出impossible,如果(tot+数组b的元素数量)<k,说明拆掉连通块内部的所有边改为连向s,也不够k条边与s相连,同样抛出impossible

将数组b中的元素从小到大排序,选择前k-tot个元素,将这些值加入ans,表示将连通块内部的一条边拆去改为一条连向s的边,使得s满足与其相连的边的数量是k

#include<bits/stdc++.h>
using namespace std;
using ll=long long ;

const ll maxm=5e5+5,maxn=5e4+5,inf=0x3f3f3f3f3f3f3f3f;
ll n,m,s,k,cnt;
ll val[maxn],S[maxn];
struct edge{
    ll u,v,w;
    edge(ll u=0,ll v=0,ll w=0) : u(u),v(v),w(w) {}
    bool operator < (const edge &rhs) const {
        return w<rhs.w;
    }
}e[maxm];

ll find_set(ll x){
    if(S[x]==x) return x;
    return S[x]=find_set(S[x]);
}

int main()
{
    ios::sync_with_stdio(0);cin.tie(0);

    cin>>n>>m>>s>>k;
    memset(val,0x3f,sizeof(val));
    cnt=0;
    for(ll i=1;i<=m;i++){
        ll u,v,w;cin>>u>>v>>w;
        if(v==s) val[u]=min(val[u],w);
        else if(u==s) val[v]=min(val[v],w);
        else {
            e[++cnt]=edge(u,v,w);
        }
    }
    //使用Kruskal算法对与s无关的边构建MST
    ll ans=0;
    vector<ll> b;
    for(ll i=1;i<=n;i++) S[i]=i;
    stable_sort(e+1,e+1+cnt);
    for(ll i=1;i<=cnt;i++){
        ll u=e[i].u,v=e[i].v,w=e[i].w;
        ll x=find_set(u),y=find_set(v);
        if(x==y) continue;
        if(val[x]<val[y]) swap(x,y);
        S[x]=y;
        ans+=w;
        if(val[x]!=inf) b.push_back(val[x]-w);
    }
    //统计现在生成森林中有多少连通块
    ll tot=0;
    for(ll i=1;i<=n;i++){
        if(S[i]!=i || i==s) continue;
        if(val[i]==inf) {
            cout<<"Impossible"<<"\n";
            goto END;
        }
        ans+=val[i];
        tot++;
    }
    if(tot>k || tot+b.size()<k) {
        cout<<"Impossible"<<"\n";
        goto END;
    }
    stable_sort(b.begin(),b.end());
    for(ll i=0;i<k-tot;i++){
        ans+=b[i];
    }
    cout<<ans<<"\n";
    END:
    return 0;
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值