1. 程式人生 > 實用技巧 >P3037 [USACO11DEC]Simplifying the Farm G[最小生成樹]

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

前言

\(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);
}