首先kruskal模版打一下(并查集维护连通块)
按照相同边权的分成一组
我们需要三个计数变量:
cnt1是当前组有几条边可以用(在还没有使用任何本组的边时,有几条连接了不同的两个连通块)
cnt2是有几对不同的连通块被连接了(程序用set维护,自动去重,set的大小就是有几对不同的)
cnt3是最后需要用几条边来连通这几个连通块(当然是能少用就少用,由于边权相同,不用在乎到底用了哪几条,才会出现有多个最小生成树的状况)
接下来开始讨论不同的合并状况(乘法原理)
1.有三条边可用
需要一条边 选择一条 3种(不用担心选某一条会导致不满足连通,负责merge的循环保证了一条就能连通,也就是说只有两个不同的连通块,其他两条边也是连接这两个连通块的)
需要两条边:
a.有三对连通块需要被连通(由于用两条边就行,肯定是下图情况) 三选二 3种
b.有两对连通块要被连通 一条必连,另外两条二选一 2种
2.有两条边可以用
需要一条边 选择一下 2种
需要两条边 两条都用上 1种
只有一条边可以用就对个数没什么贡献了
具体的实现和细节就看代码啦(主要步骤前面都有提及)
提示:n是点数,m是边数,e存边,fa存并查集,ans存最小生成树边权和,cnt存最小生成树个数
#define mod 1000000007
int n,m,fa[40005];
struct edge{
int a,b,c;
} e[100005];
long long ans,cnt=1;
bool cmp(edge x,edge y){ return x.c<y.c; }
int find(int x){
if(fa[x]==x) return x;
return fa[x]=find(fa[x]);
}
int merge(int x,int y){
x=find(x),y=find(y);
if(x==y) return 0;
fa[y]=x;
return 1;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++) scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].c);
sort(e+1,e+m+1,cmp);
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i){
set<pair<int,int> > s;
pair<int,int> p;
int j,cnt1=0,cnt3=0;
for(j=i;j<=m&&e[i].c==e[j].c;j++){
int x=find(e[j].a),y=find(e[j].b);
if(x==y) continue;
else{
p.first=min(x,y),p.second=max(x,y);
cnt1++;
s.insert(p);
}
}
for(i;i<j;i++) cnt3+=merge(e[i].a,e[i].b);
ans+=e[i-1].c*cnt3;
if(cnt1==2&&cnt3==1) cnt=cnt*2%mod;
if(cnt1==3&&cnt3==1) cnt=cnt*3%mod;
if(cnt1==3&&cnt3==2&&s.size()==3) cnt=cnt*3%mod;
if(cnt1==3&&cnt3==2&&s.size()==2) cnt=cnt*2%mod;
}
printf("%lld %lld\n",ans,cnt);
return 0;
}
标签:连通,2522%,kruskal,个数,最小,生成,同边权,三条,两条
From: https://blog.csdn.net/2401_84512298/article/details/139551654