[JOISC 2016 Day3 T3]「電報」基環樹 + 拓撲判環

題目描述

友情鏈接:https://loj.ac/problem/2737
題目描述
題目譯自 JOISC 2016 Day3 T3「 電報
給出 n 個點,每個點的出度均爲 1,給出這 n 個點初始指向的點Ai ,和改變這個點指向的目標所需要的價值 Ci
求讓所有點強連通的最小花費。

輸入格式
第一行輸入一個數 n 表示點的個數。
之後的 n 行每行兩個數 Ai,Ci 表示第 i 個點指向第 Ai 個點,更改該點指向的點花費爲 Ci

輸出格式
共一行,爲讓所有點強連通的最小花費。

樣例輸入輸出

Sample Input 1
4
2 2
1 4
1 3
3 1
Sample Output 1
4

Sample Input 2
4
2 2
1 6
1 3
3 1
Sample Output 2
5

Sample Input 3
4
2 2
1 3
4 2
3 3
Sample Input 3
4

Sample Input 4
3
2 1
3 1
1 1
Sample Output 4
0

題解

(講的有點混亂,理解一下。。。)
看了看題目所求,又看了看輸入格式,然後發現,想要形成一個強連通,那不是要形成一個環嗎?!!
既然如此,那麼我們就可以開始進一步思考了,
對於任意一個圖而言,我們都可以將它拆分成幾個不同的部分,這些部分的結構只能是環(單獨的一個點也算作是環)或者是樹,因此,這個圖是一個基環樹
在這裏插入圖片描述
那麼, 爲了能讓一堆基環樹連成一個環,首先就是要斷邊,那麼斷哪些邊捏??
接下來,我們進行分類討論:

  • 對於任意一個環上的點,我們需要讓它只存在於本環,不存在於其它樹(可以想到,它只會存在於一個環)
  • 對於任意一棵樹上的點,我們需要讓它只存在於本樹,與環不再有任何瓜葛。。。

知道了該怎麼斷邊的斷邊的效果,就該思考怎麼斷掉這些邊了。
(盯着上面那副圖。。。)

  • 對於第一種情況,我們以節點2爲例:我們需要斷掉 5到2的邊 以及 6到2的邊 ,使節點2只存在於環中,節點5和節點6成爲一條獨立的鏈(一個點也是鏈)
    簡單點說,就是需要斷掉環上點與樹上點相連的邊
  • 對於第二種情況,我們以節點9爲例:爲了使它只存在於一條鏈當中,我們需要斷掉它與節點1相連的邊,成爲獨立的一個點(這圖畫得不太好,理解就好);如果它與其它非環上的點還有邊相連,那麼也需斷掉多餘的邊,使每個節點有且僅有一條入邊(題目保證了它只會有一條出邊)
    一言以畢之,樹上點只能保存一條入邊,多餘的邊都要刪掉

接下來,我們繼續思考它們的貢獻
(還是上面那幅圖。。。還是相似的分類討論)

  • 對於節點2而言,我們需要將除了3到2的邊和2到1的邊留下,其它的都需要街道其它節點,因此,我們需要加上修改這兩條邊的貢獻;
    簡而言之,除了與本環上點相連的邊,都需要破掉,即累加這些邊的貢獻
  • 對於節點9而言,它需要破掉指向節點1的邊(在判斷節點1時會被破掉,但判斷節點9時不會有影響)
    總的來說,就是要破掉多餘的邊,使之只剩一條入邊,一條出邊(或是單獨一個點)

破完邊之後,又出現了一個新問題:
這些破掉的邊,最後會指向哪個節點?會不會就是原本指向的節點?
完全有可能!
因此,我們需要思考,“復活”一些邊,使之成爲一個環。
根據最優策略,我們需要復活消耗最高的那些邊,才能使的答案花費最小。
至此,代碼就可以出爐了

參考代碼

#include <cstdio>
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
#define INf 0x7f7f7f7f

const int N = 1e5;
int n, nxt[N + 5], cost_[N + 5], in[N + 5], isCircle[N + 5], f[N + 5], cnt;
//isCircle[]:-1--該節點不在環上;0--該節點在環上但尚未確定在哪個環;1--已經確定了該節點位於哪個環上
long long ans;
bool flag;
vector < int > G[N + 5], circle[N + 5];
queue < int > q;

void init () {
	scanf ("%d", &n);
	for (int i = 1; i <= n; ++ i) {
		scanf ("%d %d", &nxt[i], &cost_[i]);
		G[nxt[i]].push_back( i );
		//這裏建的是反圖,因爲我們可以發現:
		//所有的刪邊操作刪掉的都是對於單個節點來說的入邊(不善言辭,敬請諒解。。。)
		++ in[nxt[i]];
	}
}

//拓撲排序找環
void topSort () {
	for (int i = 1; i <= n; ++ i)
		if (! in[i]) {
			q.push( i );
			isCircle[i] = -1;
			flag = true;
		}
	while (! q.empty()) {
		int u = q.front();
		q.pop();
		-- in[nxt[u]];
		if (! in[nxt[u]]) {
			q.push( nxt[u] );
			isCircle[nxt[u]] = -1;
		}
	}
} 

//刪邊
void cut () {
	for (int i = 1; i <= n; ++ i) {
		if (isCircle[i] == -1) {
			int maxx = 0;
			for (int j = 0; j < G[i].size(); ++ j) {
				int v = G[i][j];
				maxx = max (maxx, cost_[v]);
				ans += cost_[v];
			}
			ans -= maxx;
		}
		else {
			for (int j = 0; j < G[i].size(); ++ j) {
				int v = G[i][j];
				if (isCircle[v] == -1) {
					ans += cost_[v];
					f[i] = max (f[i], cost_[v]);
				}
			}
			if (isCircle[i])
				continue;
			++ cnt; 
			while (! isCircle[i]) {
				circle[cnt].push_back( i );
				isCircle[i] = 1;
				i = nxt[i];
			}
		}
	}
}

//計算最終的答案
void sovle () {
	for (int i = 1; i <= cnt; ++ i) {
		int minn = INf;
		for (int j = 0; j < circle[i].size(); ++ j) {
			int v = circle[i][j];
			minn = min ( minn, cost_[v] - f[nxt[v]] );
		} 
		if (minn >= 0)
			ans += minn;
		else 
			for (int j = 0; j < circle[i].size(); ++ j) {
				int v = circle[i][j];
				if (cost_[v] - f[nxt[v]] < 0)
					ans += cost_[v] - f[nxt[v]];
			}
	}
}

int main () {
	init ();
	topSort ();
	cut ();
	//這張圖不全是環或者不只有一個環
	if (flag || cnt > 1)
		sovle ();	
	printf ("%lld\n", ans);
	return 0;
} 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章