D144 最小生成树 Kruskal 算法 P3037 [USACO11DEC] Simplifying the Farm G
D144 最小生成树 Kruskal 算法 P3037 [USACO11DEC] Simplifying the Farm G_哔哩哔哩_bilibili
P3037 [USACO11DEC] Simplifying the Farm G - 洛谷
相同的长度值 $c_i$ 最多出现三次,求最小生成树的总边长度和不同最小生成树的数量。
思路
最小生成树的总边长度可以直接跑 Kruskal,不同最小生成树的数量与 “相同的长度值最多出现三次” 有关
每种边的数量有限,边权排序后,相同的边权会聚到一起,从小到大枚举时分类讨论
如果两条边加入会构成环,那么一定不合法
用 $s_1$ 统计相同权值的边数,用 $s_2$ 统计可以加入生成树的边数,有重边时还会不同,所以开个 $set$ 去重统计相同权值的边数
那么,最小生成树的边权和 $sum+=w_i*s_2$,下面看方案数 $cnt$ 的分讨
1. 如果 $s_1=2$,$s_2=2$,那么方案数不变
如果 $s_1=2$,$s_2=1$,说明两条加入会构成环,所以任选一条加入,方案数 $cnt*2$
2. 如果 $s_1=3$,$s_2=3$,那么方案数不变
如果 $s_1=3$,$s_2=1$,说明任选两条加入都会构成环,所以任选一条加入,方案数 $cnt*3$

如果 $s_1=3$,$s_2=2$,如果 $set$ 存了 $2$ 条,说明有两条是重边,方案数 $cnt*2$
如果 $s_1=3$,$s_2=2$,如果 $set$ 存了 $3$ 条,说明任选两条加入都行,方案数 $cnt*3$

统计时用双指针向右跑,快指针统计 $s_1$,慢指针统计 $s_2$
相关板子:
D08【模板】最小生成树 Kruskal 算法 - 董晓 - 博客园
// 最小生成树 Kruskal 算法 O(MlogM) #include<bits/stdc++.h> using namespace std; const int N=1e5+5,mod=1e9+7; int n,m,fa[N]; struct E{int x,y,w;}e[N]; long long sum,cnt=1; int find(int x){ return fa[x]==x?x:fa[x]=find(fa[x]); } void merge(int x,int y){ fa[find(x)]=find(y); } signed main(){ scanf("%d%d",&n,&m); for(int i=1;i<=m;i++)scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].w); for(int i=1;i<=n;i++)fa[i]=i; sort(e+1,e+m+1,[&](E a,E b){return a.w<b.w;}); for(int i=1,j,s1,s2; i<=m;){ set<pair<int,int> >s; j=i,s1=0,s2=0; while(j<=m && e[i].w==e[j].w){ //如果边权相同 int x=find(e[j].x),y=find(e[j].y); if(x>y) swap(x,y); //保证起点小,终点大 if(x!=y){ //不在一个集合 s1++; //累计边权相同的边数 s.insert({x,y}); //set去重 } j++; //快指针j右移 } while(i<j){ if(find(e[i].x)!=find(e[i].y)){ merge(e[i].x,e[i].y); //加入生成树 s2++; //累计可以加入生成树的边数 } i++; //慢指针i右移 } (sum+=e[i-1].w*s2)%=mod; //累加最小生成树的边权 if(s1==2&&s2==1) cnt=(cnt*2)%mod; if(s1==3){ if(s2==1) cnt=(cnt*3)%mod; if(s2==2) cnt=(cnt*s.size())%mod; } //累乘最小生成树的方案数 } printf("%d %d\n",sum,cnt); }
浙公网安备 33010602011771号