先不要看点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;
}
433

被折叠的 条评论
为什么被折叠?



