# Union-Find# Sorting# Greedy# Graph

f506 - 11747 - Heavy Cycle Edges

🔗 前往 ZeroJudge 原題

題目描述

題目要求找出一個無向圖中,在構建最小生成樹的過程中,所有被移除的邊的權重。移除的邊是那些屬於循環,且在循環中權重最大的邊。如果圖中沒有循環,則輸出 "forest"。

解題思路

本題的核心思想是利用 Kruskal 算法的思想,但反向操作。Kruskal 算法是從最小的邊開始添加,直到形成一個最小生成樹。而本題是從所有邊開始,然後逐步移除循環中權重最大的邊。

  1. 初始化: 使用 Union-Find 資料結構來追蹤圖的連通性。初始時,每個節點都是一個獨立的集合。
  2. 排序: 將所有邊按照權重從小到大排序。
  3. 迭代: 遍歷排序後的邊。
    • 如果邊的兩個端點不在同一個集合中,則將它們合併,表示添加了這條邊。
    • 如果邊的兩個端點在同一個集合中,則表示添加這條邊會形成一個循環。此時,這條邊就是循環中權重最大的邊,需要移除,並將其權重記錄下來。
  4. 輸出: 將所有被移除的邊的權重按照從小到大的順序輸出。如果沒有移除任何邊,則輸出 "forest"。

複雜度分析

  • 時間複雜度: O(M log M + M α(N)),其中 M 是邊的數量,N 是節點的數量,α(N) 是反阿克曼函數,在實際應用中可以視為常數。排序邊需要 O(M log M) 的時間,Union-Find 操作(find 和 union)平均時間複雜度為 O(α(N)),總共需要 M 次操作。
  • 空間複雜度: O(N + M),其中 N 是節點的數量,M 是邊的數量。需要 O(N) 的空間來儲存 Union-Find 資料結構,O(M) 的空間來儲存邊。

程式碼

#include <iostream>
#include <algorithm>
using namespace std;
int fa[1005],n,m,k;
pair <int,pair <int,int> > edge[30005];
int ff(int x){
	if(fa[x]==x)return x;
	return fa[x]=ff(fa[x]);
}
void merge(int x,int y){
	x=ff(x);
	y=ff(y);
	if(x==y)return;
	fa[x]=y;
}
int main(){
	cin.tie(0); ios::sync_with_stdio(false);
	while(cin >> n >> m){
		if(n==0&&m==0)break;
		int ac=0;
		for(int i=0;i<=n;++i){
			fa[i]=i;
		}
		for(int i=0;i<m;++i){
			cin >> edge[i].second.first >> edge[i].second.second >> edge[i].first;
		}
		sort(edge,edge+m);
		for(int i=0;i<m;++i){
			if(ff(edge[i].second.first)!=ff(edge[i].second.second)){
				merge(edge[i].second.first,edge[i].second.second);
			}
			else{
				if(ac)cout << " ";
				cout << edge[i].first;
				ac=1;
			}
		}
		if(ac==0){
			cout << "forest";
		} 
		cout << "\n";
	}
}

Discussion