The 15th Chinese Northeast Collegiate Programming Contest K. City (最小生成树计数,离线询问)
-
题意:有一张\(n\)个点,\(m\)条边的无向图,每条边都有边权,\(q\)个询问,每次问一个\(q_i\),将所有边权减去\(p_i\)后不小于\(0\)的边为有效边,问有多少对点能相互到达。
-
题解:一张图,两个点连通,不难想到最小生成树,进而想到保留边权最大的边最优,即转化成了最大生成树,那么我们就可以将询问离线存下来从小到大排序,然后跑kruskal同时二分找到满足条件的最大询问,贡献给对应询问的答案。最后再跑个后缀把空的位置填起来。
-
代码:
#include <bits/stdc++.h> #define ll long long #define fi first #define se second #define pb push_back #define me memset #define rep(a,b,c) for(int a=b;a<=c;++a) #define per(a,b,c) for(int a=b;a>=c;--a) const int N = 1e6 + 10; const int mod = 1e9 + 7; const int INF = 0x3f3f3f3f; using namespace std; typedef pair<int,int> PII; typedef pair<ll,ll> PLL; ll gcd(ll a,ll b) {return b?gcd(b,a%b):a;} ll lcm(ll a,ll b) {return a/gcd(a,b)*b;} #define int long long struct misaka{ int a,b; int val; }edge[N]; int n,m,q; int p[N]; ll sz[N]; int find(int x){ if(p[x]!=x) p[x]=find(p[x]); return p[x]; } signed main() { int _; scanf("%lld",&_); while(_--){ scanf("%lld %lld %lld",&n,&m,&q); for(int i=1;i<=m;++i){ int u,v,val; scanf("%lld %lld %lld",&u,&v,&val); edge[i]={u,v,val}; } vector<PLL> query(q+1); vector<int> res(q+1); for(int i=1;i<=q;++i){ scanf("%lld",&res[i]); query[i].fi=res[i]; query[i].se=i; } sort(query.begin()+1,query.end()); sort(res.begin()+1,res.end()); sort(edge+1,edge+1+m,[&](misaka x,misaka y){ return x.val>y.val; }); for(int i=1;i<=n;++i) p[i]=i,sz[i]=1; vector<ll> ans(q+1); ll sum=0; for(int i=1;i<=m;++i){ int a=edge[i].a; int b=edge[i].b; int val=edge[i].val; int fa=find(a); int fb=find(b); if(fa!=fb){ p[fa]=fb; int pos=upper_bound(res.begin()+1,res.end(),val)-res.begin()-1; sum+=sz[fa]*sz[fb]; ans[query[pos].se]=sum; sz[fb]+=sz[fa]; } } for(int i=q-1;i>=1;--i){ ans[query[i].se]=max(ans[query[i].se],ans[query[i+1].se]); } for(int i=1;i<=q;++i){ printf("%lld\n",ans[i]); } } return 0; }
𝓐𝓬𝓱𝓲𝓮𝓿𝓮𝓶𝓮𝓷𝓽 𝓹𝓻𝓸𝓿𝓲𝓭𝓮𝓼 𝓽𝓱𝓮 𝓸𝓷𝓵𝔂 𝓻𝓮𝓪𝓵
𝓹𝓵𝓮𝓪𝓼𝓾𝓻𝓮 𝓲𝓷 𝓵𝓲𝓯𝓮