D145 最小生成树 Kruskal 算法 P14362 [CSP-S 2025] 道路修复
D145 最小生成树 Kruskal 算法 P14362 [CSP-S 2025] 道路修复_哔哩哔哩_bilibili
n 个城市,m 条边,k 个乡镇,每个乡镇都可以改造,都可以与 n 个城市直接连边,求连通 n 个城市的最小费用。
最大 $n=10^4,m=10^6,k=10$
思路
该选择哪些乡镇改造,又连哪些边呢?
$k$ 只有 $10$,可以状态枚举,从 $0$ 到 $2^k-1$,例如,$(00001011)_2$ 表示选择乡镇 $1,2,4$
对于 $m$ 条边,先跑一遍 Kruskal,挑出 $n-1$ 条最小边,其它无用。时间复杂度为 $O(mlog(m))$
然后,把乡镇与城市的 $kn$ 条连边加进来,对 $n-1+kn$ 条边做个预排序。时间复杂度为 $O(knlog(kn))$
每次状态枚举,跑一遍 Kruskal,累加边权,注意去掉没选的乡镇。不用排序了,时间复杂度为 $O(2^k*kn)$
从每次结果中选出最小值,得出最终答案,图中的最小费用 $=1+1+2+4+5=13$

相关板子:
D08【模板】最小生成树 Kruskal 算法 - 董晓 - 博客园
// 最小生成树+状态枚举 O(mlogm+knlogkn+2^k*kn) #include<bits/stdc++.h> using namespace std; int read(){ int f=1,x=0; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=10*x+c-'0'; return f*x; } const int N=10005,M=1000005; struct E{int u,v,w;}e[M]; //边集 int fa[N],c[12],vis[12]; int find(int u){ return fa[u]==u?u:fa[u]=find(fa[u]); } int main(){ int n=read(),m=read(),k=read(); for(int i=1;i<=m;i++)e[i]={read(),read(),read()}; sort(e+1,e+1+m,[&](E a,E b){return a.w<b.w;}); for(int i=1;i<=n;i++) fa[i]=i; int tot=0; for(int i=1;i<=m;i++){ int u=find(e[i].u),v=find(e[i].v); if(u!=v){ fa[v]=u; e[++tot]=e[i]; if(tot==n-1) break; } } //Kruskal for(int i=1;i<=k;i++){ c[i]=read(); for(int j=1;j<=n;j++)e[++tot]={n+i,j,read()}; } //连城市到乡镇的边 sort(e+1,e+1+tot,[&](E a,E b){return a.w<b.w;}); long long ans=1e18; for(int st=0;st<(1<<k);st++){ //枚举k个点的选择状态 long long sum=0; int num=0; for(int i=1;i<=k;i++){ //1011:选第1,2,4乡镇 if((st>>(i-1))&1){ ++num; //选择乡镇的个数 vis[i]=1; //选择乡镇i sum+=c[i]; //加上点权 } else vis[i]=0; } for(int i=1;i<=n+k;i++) fa[i]=i; int cnt=0; for(int i=1;i<=tot;i++){ //tot=n-1+kn int u=e[i].u,v=e[i].v; if(u>n && !vis[u-n]) continue; //乡镇u没选 u=find(u),v=find(v); if(u!=v){ fa[v]=u; sum+=e[i].w; //累加边权 if(++cnt==n+num-1) break; } } ans=min(ans,sum); //各种方案中的最小值 } cout<<ans; }
浙公网安备 33010602011771号