1. 程式人生 > >#100-【最小生成森林(最小生成樹變種)】灌水

#100-【最小生成森林(最小生成樹變種)】灌水

#100祭!

Description

Farmer John已經決定把水灌到他的n(1<=n<=300)塊農田,農田被數字1到n標記。把一塊土地進行灌水有兩種方法,從其他農田飲水,或者這塊土地建造水庫。 建造一個水庫需要花費wi(1<=wi<=100000),連線兩塊土地需要花費Pij(1<=pij<=100000,pij=pji,pii=0). 計算Farmer John所需的最少代價。

Input

第一行:一個數n

第二行到第n+1行:第i+1行含有一個數wi

第n+2行到第2n+1行:第n+1+i行有n個被空格分開的數,第j個數代表pij。

Output

第一行:一個單獨的數代表最小代價.

Sample Input

4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0

Sample Output

9

HINT

【輸出詳解】 Farmer John在第四塊土地上建立水庫,然後把其他的都連向那一個,這樣就要花費3+2+2+2=9

先是0(超級源點)到i連邊長度wi,隨後正常建圖即可。

#include <iostream>
#include <vector>
#include <algorithm>

#define SIZE 310

using namespace std;

struct edge
{
	int from, to, dis;
};

vector<edge> graph;
int pre[SIZE];

bool comp(edge a, edge b)
{
	return a.dis < b.dis;
}

int find(int x) // 找祖先(並查集操作)
{
	return (pre[x]) ? pre[x] = find(pre[x]) : x; // 順便狀態壓縮
}

int main(int argc, char** argv)
{
	int n, i, j, x, res = 0, c = 0, u, v, w, prex, prey;
	
	scanf("%d", &n);
	for (i = 1; i <= n; ++i)
	{
		scanf("%d", &x);
		graph.push_back({0, i, x}); // 連0點(最小生成森林建圖)
	}
	for (i = 1; i <= n; ++i) // 正常建圖
	{
		for (j = 1; j <= n; ++j)
		{
			scanf("%d", &x);
			if (i <= j)
			{
				graph.push_back({i, j, x});
			}
		}
	}
	
	sort(graph.begin(), graph.end(), comp); // 注意vector型別要用vector::begin()和vector::end()函式作為開頭和結尾
	for (i = 0; i < graph.size(); ++i) // K演算法求最小生成樹
	{
		u = graph[i].from;
		v = graph[i].to;
		w = graph[i].dis;
		prex = find(u); // 看建這條邊是否有必要(並查集操作)
		prey = find(v);
		if (prex ^ prey) // ^是位運算異或符,這裡是不等於(!=)的意思
		{
			res += w;
			if (++c == n) // 連了n(= n + 1 - 1)條邊,直接退出
			{
				break;
			}
			pre[prex] = prey;
		}
	}
	
	printf("%d", res);
	
	return 0;
}