UVa 818 Cutting Chains 题解

Posted alrond

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UVa 818 Cutting Chains 题解相关的知识,希望对你有一定的参考价值。

难度:β

建议用时: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 题解的主要内容,如果未能解决你的问题,请参考以下文章

UVA818-Cutting Chains(二进制枚举+dfs判环)

UVA 818 Cutting Chains

UVa818 Cutting Chains (二进制枚举)

UVA10003 Cutting Sticks

Cutting Sticks UVA - 10003

UVA10003 Cutting SticksDP