P3037 [USACO11DEC]Simplifying the Farm G[最小生成树]

女生的网名这么多〃 提交于 2020-08-04 19:29:30

前言

\(Kruscal\)的进一步应用以及\(set\)去重应用,输入输出没翻译,练习一下英语水平吧(其实是懒得搞)(逃

题目描述

Farmer John has been taking an evening algorithms course at his local university, and he has just learned about minimum spanning trees. However, Farmer John now realizes that the design of his farm is not as efficient as it could be, and he wants to simplify the layout of his farm.

The farm is currently arranged like a graph, with vertices representing fields and edges representing pathways between these fields, each having an associated length. Farmer John notes that for each distinct length, at most three pathways on his farm share this length. FJ would like to remove some of the pathways on his farm so that it becomes a tree -- that is, so that there is one unique route between any pair of fields. Moreover, Farmer John would like this to be a minimum spanning tree -- a tree having the smallest possible sum of edge lengths.

Help Farmer John compute not only the sum of edge lengths in a minimum spanning tree derived from his farm graph, but also the number of different possible minimum spanning trees he can create.

农夫约翰在一所夜校学习算法课程,他刚刚学会了最小生成树。现在约翰意识到他的农场设计得不够高效,他想简化农场的布局。

约翰的农场可以看做一个图,农田代表图中顶点,田间小路代表图中的边,每条边有一定的长度。约翰注意到,农场中最多有三条小路有着相同的长度。约翰想删除一些小路使得农场成为一棵树,使得两块农田间只有一条路径。但是约翰想把农场设计成最小生成树,也就是农场道路的总长度最短。

请帮助约翰找出最小生成树的总长度,同时请计算出总共有多少种最小生成树?

输入格式

  • Line \(1\): Two integers \(N\) and \(M\) (\(1\le N\le 40,000; 1\le M\le 100,000\)), representing the number of vertices and edges in the farm graph, respectively. Vertices are numbered as \(1..N\).

  • Lines \(2..M+1\): Three integers \(a_i, b_i\) and \(n_i\) (\(1\le a_i, b_i\le N; 1\le n_i\le 1,000,000\)) representing an edge from vertex \(a_i\) to \(b_i\) with length \(n_i\). No edge length \(n_i\) will occur more than three times.

输出格式

  • Line \(1\): Two integers representing the length of the minimal spanning tree and the number of minimal spanning trees (\(mod\) \(1,000,000,007\)).

输入输出样例

输入

4 5
1 2 1
3 4 1
1 3 2
1 4 2
2 3 2




输出

4 3

说明/提示

Picking both edges with length \(1\) and any edge with length \(2\) yields a minimum spanning tree of length \(4\).

分析

题意

相信翻译说的已经挺明白的了,就是求一个有相同边权的图上的最小生成树的整体的权值,并且求出方案数。

正题

最小生成树的整体的权值很好求吧,其实就是一个\(Kurscal\)的板子,先升序排序,按顺序用并查集求(不会\(Kruscal\)的应该不会来做这个题吧……这里不多赘述)这样第一问就求出来了。

那么关键的第二问来了:
首先我们考虑一下,假如有相同的边权,并且可以作为最小生成树的一条边,那么我们就让数量加一,每次只需要依次遍历相同边权的边即可。

因为最多有三条长度相同的,但是我们不能确定他们是不是一样的,所以我们就需要去重操作,而一样的条件则是连接的点相同,所以要用到\(pair\)\(set\)\(pair\)进行记录连接的两个点,而\(set\)存储,由于它本身就带有去重的操作,所以最后我们只需要统计他的不同的个数来进行分情况考虑就行了。

需要注意的一个地方就是要是这个边权只加入生成树一个边,那么就直接让情况数乘以上边记录的相同边权的个数,也就是\(cnt\)

其次就是这个边权加入生成树两个边,因为加入三个边只有一种情况,所以考虑两个边的就行。如果上边当前权值有\(3\)个边,并且没有重复的(利用\(set\)的去重和\(size\)函数)那么情况数就乘以\(3\),如果有三个边但是有两个是重复的,那么就乘以\(2\),其他的情况都是方案不变的,这样我们就得到了答案。

代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 1e6+10;
const int mod = 1e9+7;
int n,m;
struct Node{
	int x,y,val;
}e[maxn<<1];
int fa[maxn];
int ans,sum=1;
int cnt[maxn];
bool cmp(Node a,Node b){
	return a.val<b.val;
}//按边权排序
int Find(int x){
	return x == fa[x] ? x : fa[x] = Find(fa[x]);
}//并查集查找
void kruscal(){
	for(int i=1;i<=n;++i){//并查集初始化
		fa[i] = i;
	}
	sort(e+1,e+m+1,cmp);//升序排序
	for(int i=1;i<=m;){
		int cnt = 0;//记录每个边权的个数
		set<pair<int,int> >s;//set用于存储并去重
		int j;
		for(j = i;j<=m && e[i].val == e[j].val;++j){//枚举相同的边,更新j
			int x = Find(e[j].x);
			int y = Find(e[j].y);
			if(x > y)swap(x,y);
			if(x != y){//记录
				cnt ++;
				s.insert(make_pair(x,y));
			}
		}
		int num = 0;
		for(;i<j;++i){//继续枚举,更新i
			int x = Find(e[i].x);
			int y = Find(e[i].y);
			if(x != y){//加边
				fa[y] = x;
				num++;
			}
		}
		ans += e[i-1].val*num;
		if(num == 1){//如果就加入一条边,那么直接乘以相同边权的数量
			sum = sum * cnt % mod
		}
		if(num == 2){//加入两条边
			if(cnt == 3 && s.size() == 2){//有两个等价的边
				sum = 2*sum%mod;
			}
			if(cnt == 3 && s.size() == 3){//没有等价的边
				sum = 3*sum%mod;
			}
		}//其他情况的话方案数都不变
	}
}
signed main(){
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=m;++i){//存边
		scanf("%lld%lld%lld",&e[i].x,&e[i].y,&e[i].val);
	}
	kruscal();//最小生成树
	printf("%lld %lld\n",ans,sum);
}

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!