1. 程式人生 > >UVa 818 Cutting Chains 題解

UVa 818 Cutting Chains 題解

urn 了吧 virt gre rtai body 判斷 叠代 more

難度:β

建議用時:40 min

這題應該有叠代加深搜索的解法的,但我參考網友做法,用暴力枚舉法。

大致思路是:枚舉圓環的每種開閉狀態,統計符合要求的最小的打開的圓環的數量。

要判斷打開圓環的某一種方法是否符合要求,容易想到的一個是先判斷除去這些已打開的圓環外的閉合圓環有沒有組成一個環。

如果還有環,那麽無論把打開的圓環怎樣重新連城一條鏈套回去,都不能消除環,而題目要求我們夠一條鏈。所以如果任然存在環的方法是不行的。

0000 0 (此時還有一個環沒打開,不能組成鏈)

0 0

0000

當我們判斷好剩下的環沒有互相套成圈後,並不能充分說明這個方法就是符合要求的,就是可以統計開環數的。

0000 0000 0000 0

像上面那樣,如果只有一個開環,三個鏈,因為只可以把單個環放回鏈的某個位置,而不能讓兩個鏈首尾相接(因為鏈的頭尾沒有打開),所以環太少而鏈太多的方法也是不行的。

現在可以充分說明這個方法有效了吧?(@@)

NO!

要知道如果原先的鏈十分錯綜復雜的連在一起,那麽有可能當我們拆下某些環後,有這樣的情況:

00‘0’00 0 0

0

0

註意打印號的環。在這種情況中,雖然沒有環,也可以保證讓整個環集連在一起且不會構成環,但是有一個環同時連著三個其他的環。這顯然不是一條鏈。

0000000000000000

這才是鏈。

現在考慮了上述三種情況:1)沒圈 2)多環少鏈 3)沒“交際花”

這時候滿足上述三種條件的拆環方法因該就可以被充分判斷為有效的吧。可以統計了。

來看看具體實現吧。

這題因為只涉及到最多 15 個圓環,自然聯想到用二進制表示圓環的開閉狀態。

用 ”1” 對應開環,“0”表示閉環。於是枚舉方法的 code:

1 for (int select = 0; select < (1<<n); select++) {
2         for (int i = 0; i < n; i++) {
3             if ((1<<i)&select) { 
4                 cnt++;
5             }
6         }
7 }

這裏順帶計數了。

這題核心算法不難,但很麻煩。

先上 code 吧。

技術分享圖片
 1 void mark_rings(int u, int fa) {
 2     mark[u] = 1
; 3 for (int i = 0; i < id[u]; i++) if(!cut[G[u][i]] && G[u][i] != fa) { 4 mark_rings(G[u][i], u); 5 } 6 } 7 8 bool does_not_meet_condition(int number_of_cuts) { 9 // exist a ring connect with more than two other rings 10 int cnt[maxn]; memset(cnt, 0, sizeof(cnt)); 11 bool must_contains_circle = true; 12 for (int ring = 1; ring <= n; ring++) if (!cut[ring]) { 13 for (int i = 0; i < id[ring]; i++) { 14 if (!cut[G[ring][i]]) { cnt[ring]++; } 15 } 16 if (cnt[ring] > 2) return true; 17 if (cnt[ring] <= 1) must_contains_circle = false; 18 } 19 if (must_contains_circle) { return true; } // a chain without a ring connect with only one another ring is certainly a circle 20 21 int number_of_subchains = 0; 22 memset(mark, 0, sizeof(mark)); 23 for (int ring = 1; ring <= n; ring++) if (!cut[ring] && !mark[ring] && cnt[ring] <= 1) { 24 // mark all rings connect with an ‘end‘ 25 mark_rings(ring, 0); 26 number_of_subchains++; 27 } 28 for (int ring = 1; ring <= n; ring++) if (!cut[ring] && !mark[ring]) return true; // an unmarked ring implies a circle 29 30 // number of chains greater than number of cuts plus one 31 return number_of_subchains > number_of_cuts+1; 32 } 33 34 void solve() { 35 int ans = 20; 36 for (int select = 0; select < (1<<n); select++) { 37 int cnt = 0; 38 memset(cut, 0, sizeof(cut)); 39 for (int i = 0; i < n; i++) { 40 if ((1<<i)&select) { // cut number (i+1) 41 cut[i+1] = 1; 42 cnt++; 43 } 44 } 45 if (does_not_meet_condition(cnt)) continue; 46 ans = min(ans, cnt); 47 } 48 cout << "Set " << ++kase << ": Minimum links to open is " << ans << endl; 49 }
View Code

簡直看的頭疼是吧?( !!)

現在一點點分析。

首先要記下已經拆開的環。

1 cut[i+1] = 1;

然後如果滿足條件,就統計。

1 if (does_not_meet_condition(cnt)) continue;
2 ans = min(ans, cnt);

重頭戲在 “判斷條件” 函數上。

一步步來。

我們要判斷的條件是:1)沒圈 2)多環少鏈 3)沒“交際花”

我用數組存邊,速度更快。struct 慢慢慢。

判斷是不是拆掉的環:

1 if (!cut[G[ring][i]])

這裏統計每個環連接其他環的數目:

1 bool must_contains_circle = true;
2     for (int ring = 1; ring <= n; ring++) if (!cut[ring]) {
3         for (int i = 0; i < id[ring]; i++) {
4             if (!cut[G[ring][i]]) { cnt[ring]++; }
5         }
6         if (cnt[ring] > 2) return true;
7         if (cnt[ring] <= 1) must_contains_circle = false;
8     }
9     if (must_contains_circle) { return true; } // a chain without a ring connect with only one another ring is certainly a circle

那麽這個 “must_contain_circle” 是什麽呢?( ??)

如果每個環都與兩個別的環(或者更多的)相鄰,那麽必定有至少一個圈。否則不一定有一個圈。這是一個優化。

下面判斷到底有沒有環。

用”染色法“。從鏈的一端(也就是 cnt 為 1 的點)開始染。

1 int number_of_subchains = 0;
2 memset(mark, 0, sizeof(mark));
3 for (int ring = 1; ring <= n; ring++) if (!cut[ring] && !mark[ring] && cnt[ring] <= 1) {
4     // mark all rings connect with an ‘end‘
5     mark_rings(ring, 0);
6     number_of_subchains++;
7 }
8 for (int ring = 1; ring <= n; ring++) if (!cut[ring] && !mark[ring]) return true; // an unmarked ring implies a circle

如果你吧所有在某根鏈上的點都染色了(包括一個單獨的點,但要弄清它與拆下的點的區別),但還是有那麽一些點沒染色,肯定有一個環。

順便記下鏈的數量,染一次加一。

染色用 dfs 搞定。註意不要陷入循環,也不要漏塗。

1 void mark_rings(int u, int fa) {
2     mark[u] = 1;
3     for (int i = 0; i < id[u]; i++) if(!cut[G[u][i]] && G[u][i] != fa) {
4         mark_rings(G[u][i], u);
5     }
6 }

??。最後一步,判斷環數與鏈數。因為一個拆環可以把兩根鏈連接起來,那麽就有 “ 鏈數 <= 環數 - 1 ”。

1 // number of chains greater than number of cuts plus one
2 return number_of_subchains > number_of_cuts+1;

註意因為我們在這裏試問符不符合條件,因此不等號要反向。

終於搞定了。建議在回頭看看總體的算法,加深印象。

這題沒什麽優化。最大的優化本該是用叠代加深的。但我太弱,沒寫。以後再補。

還有一點非常非常重要。在見圖的時候一定要一定要判重。一對環就不要連兩次了。否則 cnt 值會變大,可能誤判環。

1 void connect(int x, int y) {
2     if (!connection[x][y]) {
3         connection[x][y] = connection[y][x] = 1;
4         G[x][id[x]++] = y;
5         G[y][id[y]++] = x;
6     }
7 }

現在每一點都達到了。整理一下,測一測數據。提交,AC!

在 Virtual OJ 測的 90 ms。有大神能 0ms 刷過。因該是用了叠代加深吧。回頭看看去。

有問題留言哈,就算捧場了。

還有如果要轉載也告訴我一聲(~~)

如果你對這篇文章感興趣,請關註我,我會定期(每天)更新文章。希望一起交流哈~

2018-01-22 00:06:40

UVa 818 Cutting Chains 題解