題目描述
友情鏈接: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;
}