XTU 1261 - Roads - [最小割][2017年湘潭邀請賽(江蘇省賽)B題]
之前在網上搜了一個下午沒搜到這道題的題解,然後同時又對著叉姐寫的兩行字題解看了一個下午;
雖然基本上已經知道了這題的思路,但楞是因為自己代碼實現起來太繁復,外加不確定正確性,沒敢碼……
但是一道題肝了一下午沒肝出來,就要放棄的話,怕是太紮心了,忍不住就跑去ICPCCamp.Post問叉姐了(https://post.icpc-camp.org/d/715-2017-b-roads)
看了叉姐的對於我的幾個問題的回復,我總算肯定了我的思路,而且叉姐還在下面給了標程,當時可以說心情非常愉悅;
聽起來是非常的exciting,但是當我看到了整篇code沒用一個數組,放codeblocks裏楞是編譯不通過,看了半天楞是沒看懂的時候,我的心情瞬間原地爆炸,整個人都是崩潰的。
當然啦,主要原因還是那天整個人狀態都不是很好,心態已經屬於炸了的狀態,看不懂也是正常;
睡了兩覺,擺好心態,再回來看,就不是很難了。
嗯,口胡結束,開始正文。
萌萌的分割線
題目鏈接:http://202.197.224.59/OnlineJudge2/index.php/Problem/read/id/1261
Time Limit : 1000 MS Memory Limit : 65536 KB
In ICPCCamp, there are n towns conveniently labeled with 1,2,…,n and m bidirectional roads planned to be built. The i-th road will be built between cities a[i] and b[i] with cost c[i] . The builders in ICPCCamp will build the (n?1)
Bobo, the mayor of ICPCCamp is going to remove some of the roads from the construction plan. He would like to know the minimum number roads to be removed to strictly increases the total cost.
Note that the total cost is considered as +∞ if no valid (n?1) roads exist after removing. It is also counted as "total cost strictly increases".
Input
The input contains zero or more test cases and is terminated by end-of-file. For each test case:
The first line contains two integers n and m . The i-th of the following m lines contains a[i],b[i],c[i] .
- 2≤n≤50
- n?1≤m≤n^2
- 1≤a[i],b[i]≤n
- 1≤c[i]≤10^9
- Any two cities will be connected if all m roads are built.
- The sum of n does not exceed 10^3 .
Output
For each case, output an integer which denotes the result.
Sample Input
3 3 1 2 1 1 3 2 2 3 3 3 4 1 2 1 1 2 1 1 3 2 1 3 3 3 4 1 2 1 1 2 1 1 3 2 1 3 2 4 6 1 2 1 1 3 1 1 4 1 2 3 1 2 4 1 3 4 1
Sample Output
1 1 2 3
題意:
在ICPCCamp裏有n個城鎮(即n個點,編號為1~n),然後現在計劃建造m條雙向道路(m條無向邊,可能有重邊),每條路<a,b,c>代表這條路連接了a,b兩座城鎮,建造這條路的花費為c;
實際的建造者會挑選m條路中的n-1條進行建造,這n-1條邊滿足最小生成樹,即n-1條邊連通所有的點並且花費總和最少;
現在Bobo想要修改建造計劃,從m條計劃建造的路中,去掉一些路,得到一個新的計劃,使得建造者在這個新的計劃下造路的時候(當然,建造者依然按照上面的原則挑路來造),花費比原來大(嚴格增大)。
另外要註意的是,假如新的計劃中只剩下少於n-1條的路,那麽就會是一個非連通圖,這個時候也算作“嚴格增大”。
現在要求的是,最少移去幾條路就可以滿足“嚴格增大”;
題解:
(http://files.cnblogs.com/files/dilthey/xiangtan-2017-solution.pdf)
真的是簡潔明了的題解呢
詳細解釋一下:
分割線 begin
因為建造者肯定是按最小生成樹來造路的;那麽通過刪邊,讓最小生成樹變大的原因是什麽?
必然是刪掉了某條(或者某些)邊之後,某兩個點之間的連接被一定程度上斷開,必須要通過尋找權值更大的邊來把這兩個邊連接起來。
那麽,就很容易想到是個關於“割”的問題,又因為要刪去的邊最少,就不難聯想到有關於最小割。
就像上面的題解裏說的那樣,假設所有邊中,有相同權值的k條邊,他們連接起了一個圖(連通的,或者非連通的);
我們把這個圖割開(如果是非連通圖,就是選取某一個連通分量割開),使得存在兩個點,對於任意的weight=k的邊,都不能讓這兩個邊重新連通(直接地或間接地);
(⊙v⊙)嗯,看起來很簡單的樣子。
但是我們又會想到一點,怎麽才能確定我們斷開了某兩個點的連接之後,肯定只能用權值更大的邊來代替,而不能是權值更小的邊來代替呢?
考慮一種比較簡單的情況,假設存在edge1=<a,b>,weight=2,但是又存在edge2=<a,b>,weight=1,那麽我們如果想要刪掉edge1來增大MST,這顯然是不可能的。
意思是說……?對,我們在割圖的時候,edge1根本就不能被割,因為就算把它割開了,也等於沒割開(聽起來是如此的暴躁……
也就是說,更一般的:
那麽,怎樣才能讓最小割不把a,b割開呢?我一開始想到的是,在a,b間連一條cap=INF的邊,這固然也是可以的;
當然,之前http://www.cnblogs.com/dilthey/p/7381056.html這篇文章裏提到過,兩個點間只有一條cap=INF的邊連接的話,可以合並;
這就是https://post.icpc-camp.org/d/715-2017-b-roads/2叉姐給我的回復中提到的 “(2) 按照權值從小到大做,做完小的,把這些點縮起來。”
另外還想說的一點,是我當時想到建立一條cap=INF的邊之後的顧慮,如果原本是兩個連通分量,被我們這麽一建邊,被連到一塊兒了怎麽辦?
由於當時不確定的東西太多,腦子一團漿糊,外加心態爆炸,想到還有這種問題的時候,嚇得立馬否決了自己加cap=INF邊的想法了,
其實很簡單的,兩個連通分量,不管你用了多少條INF容量的邊連到一起,最小割也不會割到這幾條邊上去;
那麽這個時候的最小割,要麽是一刀割在連通分量1上,要麽是一刀割在連通分量2上;
而且,像叉姐說的:“(1) 如果某個權值的邊,形成的是若幹個連通塊,那麽要對每個連通塊分別跑,答案取個小的。因為我們只要割開了某個連通塊,整個圖的 MST 就變大了。”;
由於如果一個圖有多個連通分量,我們本來就要在每個連通分量的最小割裏找個最小的;
要是它們連通了,那這個時候的求出最小割,不就省的我們去一個一個連通分量去跑了嗎!?這麽簡單,當時居然沒想到,簡直了……
而如果像叉姐說那樣,把兩個點縮成一個點的話,那就更簡單了,反正最小割也不能把一個點割開來,和不能割開cap=INF的遍一樣,同樣不影響。
分割線 end
嗯,這樣思路就比較清晰了,就是:
①初始化令ans=INF,按權值從小到大枚舉邊
②所有weight==k的邊構建起一個圖(同時要做縮點操作)
③求得最小割(若為非連通圖,則求所有連通分量的最小割,取最小的),更新ans(若mincut<ans,則ans=mincut)
④再次遍歷所有weight==k的邊,若兩端點還未連通,則連通兩個端點,遍歷結束後,k++,回到②
這裏給出叉姐給的標程(附解釋):
1 #include <cstdio> 2 #include <cstring> 3 #include <climits> 4 #include <numeric> 5 #include <map> 6 #include <queue> 7 #include <utility> 8 #include <vector> 9 10 int find(std::vector<int>& parent, int u) 11 { 12 if (parent.at(u) != u) { 13 parent.at(u) = find(parent, parent.at(u)); 14 } 15 return parent.at(u); 16 }//並查集 17 18 std::vector<int> bfs(const std::vector<std::vector<int>>& cap, int s) 19 { 20 int n = cap.size(); //當前圖有n條邊 21 std::vector<int> lv(n, -1);//定義level[n]數組,並初始化均為-1 22 lv.at(s) = 0; 23 std::queue<int> q; 24 q.push(s); 25 while (!q.empty()) { 26 int u = q.front(); 27 q.pop(); 28 for (int v = 0; v < n; ++ v) { 29 if (cap.at(u).at(v) > 0 && lv.at(v) == -1) { 30 lv.at(v) = lv.at(u) + 1; 31 q.push(v); 32 } 33 } 34 } 35 //bfs跑出層次圖 36 return lv;//返回level[n]數組 37 } 38 39 int dfs(const std::vector<int>& lv, std::vector<std::vector<int>>& cap, std::vector<int>& cur, int u, int t, int left) 40 { 41 if (u == t) { 42 return left; 43 } 44 int n = cap.size(); 45 int delta = 0; 46 for (auto& v = cur.at(u); v < n; ++ v) { 47 if (cap.at(u).at(v) > 0 && lv.at(v) == lv.at(u) + 1) { 48 int tmp = dfs(lv, cap, cur, v, t, std::min(left - delta, cap.at(u).at(v))); 49 delta += tmp; 50 cap.at(u).at(v) -= tmp; 51 cap.at(v).at(u) += tmp; 52 if (delta == left) { 53 break; 54 } 55 } 56 } 57 return delta; 58 } 59 60 int main() 61 { 62 int n, m; 63 while (scanf("%d%d", &n, &m) == 2) { 64 std::map<int, std::vector<std::pair<int, int>>> edges; 65 for (int i = 0; i < m; ++ i) { 66 int a, b, c; 67 scanf("%d%d%d", &a, &b, &c); 68 edges[c].emplace_back(a - 1, b - 1); 69 } 70 int result = INT_MAX; 71 std::vector<int> parent(n); 72 std::iota(parent.begin(), parent.end(), 0);//初始化並查集 73 for (auto&& iterator : edges) {//按邊權遍歷整個存儲邊的容器 74 auto&& es = iterator.second;//取出當前邊權的所有邊,定義為es 75 std::vector<bool> mark(n); 76 std::vector<std::vector<int>> cap(n, std::vector<int>(n)); 77 for (auto&& e : es) { //遍歷es中的邊 78 auto&& a = find(parent, e.first); 79 auto&& b = find(parent, e.second); 80 if (a != b) {//如果當前邊的兩個端點還未連通 81 mark.at(a) = mark.at(b) = true;//標記這兩個點的父節點 82 cap.at(a).at(b) ++; 83 cap.at(b).at(a) ++; 84 //加入一條邊,連著兩個點的父節點(相當於之前已經連通的點,都進行了縮點) 85 //值得註意的是,這裏用了cap++,這樣可以有效地解決重邊的問題 86 } 87 } 88 for (int s = 0; s < n; ++ s) {//枚舉所有的點(並作為源點) 89 if (mark.at(s)) {//如果這個點屬於當前要跑最小割的圖 90 auto lv = bfs(cap, s);//bfs遍歷圖,標記當前連通塊的所有點 91 int t = 0; 92 while (t < s && lv.at(t) == -1) { 93 t ++; 94 }//找當前連通塊的點集,按升序排序的第一個點 95 if (t == s) { //如果這個點就是s,那代表這個連通塊還未跑過,否則這個連通塊就肯定已經跑過了 96 for (t = s + 1; t < n; ++ t) {//枚舉匯點 97 if (lv.at(t) != -1) {//如果屬於當前連通塊 98 auto fl = cap; 99 int cut = 0; 100 while (true) { 101 auto lv = bfs(fl, s); 102 if (lv.at(t) == -1) { 103 break; 104 } 105 std::vector<int> cur(n); 106 cut += dfs(lv, fl, cur, s, t, INT_MAX); 107 }//求出最大流或(者說最小割) 108 result = std::min(result, cut);//不斷更新,保證result為最小的 109 } 110 } 111 } 112 } 113 } 114 for (auto&& e : es) { //把邊加入到最小生成樹裏 115 auto&& a = find(parent, e.first); 116 auto&& b = find(parent, e.second); 117 if (a != b) { 118 parent.at(a) = b; 119 } 120 } 121 //由於縮點的關系,不加cnt==n-1的判斷條件也無所謂 122 //因為當最小生成樹完整後,後面不管什麽圖,都會被縮成一個點,也就沒有最小割了 123 } 124 printf("%d\n", result); 125 } 126 }
XTU 1261 - Roads - [最小割][2017年湘潭邀請賽(江蘇省賽)B題]