# Minimum Spanning Tree# Union-Find# Greedy# Graph

e586 - 01208 - Oreon

🔗 前往 ZeroJudge 原題

題目描述

題目描述了一個城市間隧道安全問題,目標是找到一組需要保護的隧道,使得所有城市保持連通,並且保護這些隧道所需的總人員數量最小。輸入是一個城市間隧道安全圖,圖中的數字表示保護該隧道所需的安全人員數量。

解題思路

這題可以建模為最小生成樹 (Minimum Spanning Tree) 的變形。城市可以看作圖的節點,隧道可以看作圖的邊,邊的權重是保護該隧道所需的安全人員數量。目標是找到一棵最小權重生成樹,使得所有城市連通。

程式碼使用 Kruskal 演算法來尋找最小生成樹。Kruskal 演算法的核心是:

  1. 將所有邊按照權重從小到大排序。
  2. 從權重最小的邊開始,依次考慮每一條邊。
  3. 如果加入該邊不會形成環,則將該邊加入到最小生成樹中。
  4. 重複步驟 2 和 3,直到所有節點都連通。

程式碼使用 Union-Find 資料結構來判斷加入某條邊是否會形成環。Union-Find 資料結構可以高效地判斷兩個節點是否在同一個連通分量中。如果兩個節點在同一個連通分量中,則加入它們之間的邊會形成環。

複雜度分析

  • 時間複雜度: O(E log E),其中 E 是邊的數量。排序邊需要 O(E log E) 的時間,Union-Find 演算法的每個操作需要 O(α(N)) 的時間,其中 α(N) 是反阿克曼函數,可以看作常數。因此,Union-Find 演算法的總時間複雜度是 O(E α(N)),可以近似為 O(E)。
  • 空間複雜度: O(N + E),其中 N 是節點的數量,E 是邊的數量。需要 O(N) 的空間來儲存 Union-Find 資料結構,需要 O(E) 的空間來儲存邊。

程式碼

#include <iostream>
using namespace std;
int a[27][27],all[27];
int fa(int no){
	if(all[no]==no)return no;
	all[no]=fa(all[no]);
	return fa(all[no]);
}
bool jg(int no){
	int s=0;
	for(int i=0;i<no;++i)
		if(all[i]==i){
			if(s==1)return 0;
			s=1;
		}
	return 1;
}
int main(){
	cin.tie(0); ios::sync_with_stdio(false);
	int t,n;
	char c;
	cin >> t;
	for(int ca=1;ca<=t;++ca){
		cin >> n;
		for(int i=0;i<26;++i)
			for(int j=0;j<26;++j)
				a[i][j]=0;
		for(int i=0;i<n;++i){
			for(int j=0;j<n;++j){
				cin >> a[i][j];
				if(j<n-1)cin >> c;
			}
		}
		cout << "Case " << ca << ":\n";
		for(int i=0;i<26;++i)
			all[i]=i;
		while(1){
			if(jg(n))break;
			int mn=1000000,x=-1,y=-1;
			for(int i=0;i<n;++i)
				for(int j=i+1;j<n;++j)
					if(a[i][j]&&a[i][j]<mn&&fa(all[i])!=fa(all[j])){
						mn=a[i][j];
						x=i;
						y=j;
					}
			char ax=x+'A',ay=y+'A';
			cout << ax << "-" << ay << " " << mn << "\n";
			a[x][y]=a[y][x]=0;
			int mnn=min(fa(all[x]),fa(all[y]));
			for(int i=0;i<n;++i)
				if(fa(all[i])==fa(all[x])||fa(all[i])==fa(all[y]))
					all[i]=mnn;
		}
	}
}

Discussion