您的位置:首页 > 运维架构

有向图的汇点 -- 兼 ACM PKU POJ 2186 ( Popular Cows ) 解题报告

2011-04-28 16:25 375 查看
题目链接:http://poj.org/problem?id=2186

题意:奶牛的梦想是成为牛群中最受欢迎的奶牛,即受其它所有牛的欢迎。“欢迎”是具有传递性,即如果牛A认为牛B受欢迎,牛B觉得牛C受欢迎,则牛A也隐含地认为牛C受欢迎。现在,给一组点对 (A,B) 表示 A 认为 B 受欢迎,找出有多少最受欢迎的奶牛。

这是一个有向图上的问题。用图上的顶点表示奶牛,有向边 (A,B) 表示 A 变为 B 受欢迎,则最受欢迎的牛是那些能被其它顶点所到达的顶点。最暴力的解法是,对每一个顶点,判断是否能够为其它所有的顶点所到达。这个算法的复杂度是 O(V2E),其中,V 为顶点数量,E 为有向边的数量。对于题目的数据规模,显然会超时。

实际上,存在 O(V+E) 的算法。这需要我们仔细研究和分析有向图的一些性质。

事实一:如果图上有至少两个顶点的出度为 0,则没有顶点是最受欢迎的,即答案为 0。

显然,如果这个有向图上有环,则环上的奶牛意见一致,即若 C 能被环上的某点所到达,也必然能被环上的其它任意一点所到达。

事实二:每一个强连通子图上的奶牛们意见都一致。

因此,在本题中,我们可以把每一个强连通子图(及它所代表的奶牛群)当做一个整体来看待。若把每个强连通子图看成由该子图的所有顶点能过顶点压缩 ( Vertex Contraction )成为一个超级顶点,从而得到一个新的超级图,则容易证明,

事实三:超级图是一个有向无环图 (DAG)。

强连通子图的出度定义为其对应的超级顶点在超级图上的出度,它的邻居为其对应的超级顶点在超级图上的邻居所对应的强连通子图。如果一个强连通子图的出度为0,则称其为 汇(sink)。由事实一可知,如果超级图上有至少两个汇,则没有奶牛是最受欢迎的。到目前为止,利用事实一,当没有最受欢迎的奶牛时,我们可以快速给出答案。但是,如果图没有汇,则该事实毫无用处,而且,我们还看不出其它的两个事实能怎么用来改进算法。不过,下面的事实给我们带来了希望。

事实四:任何有限 DAG 都有至少一个汇。

这个事实也不是太明显,略加证明一下。用反证法。假设没没有汇。则所有的顶点都至少一条出边,即至少有一个邻居。现在,任取一点 v1。从 v1 出发,任取 v1 的一个邻居 v2。由于每点都有出边,因此,该过程可以进行下去,从而得到路径 v1,v2,...,vn+1,其中 n 为该图的顶点数量。由于路径上有 n+1 个顶点,从而必然有至少两个顶点 vi 和 vj (i<j) 是相同的,从而 vi,vi+1,...,vj 构成了一个环,这与该图为 DAG 相矛盾。证毕。

由于超级图上存在至少一个汇,而汇是出度为 0 的顶点,即它认为其它的奶牛群都不受欢迎。因此,如果一个顶点不是汇,则它不可能是最受欢迎的!所以,我们只要考虑原来图上的汇即可。而根据事实一,我们最多考虑一个汇,因为,当有至少两个汇时,答案为 0。综合上述讨论,可以设计如下算法:

求出原图的每一个强连通子图;
求出原图的超级图;
找出超级图的所有汇;
如果有至少两个汇,则输出 0;
否则,判断该汇是否能够被其它所有超级顶点所到达。如果是,则输出该汇所对应的强连通子图的顶点数量,否则,输出 0。


第一步和第二步都只需要 O(V+E) 的时间,第三步只需要 O(V),对于第五步,可以 O(V+E) 的时间先转置一下超级图,然后看看该汇点是否能够通过转置了的超级图上的边到达所有其它顶点,这也需要 O(V+E)。因此,总的时间复杂度为 O(V+E)。

到此,问题得到很好解答。但是,并不完美。上述算法,要求强连通子图,还要求超级图,甚至超级图的转置!这需要多遍扫描原图和超级图,以及多遍的深度优先搜索!对于完美主义者来说,这似乎不能接受。虽然时间复杂度没办法再有质的降低,但是,我们可以试着降低复杂度的常数系数,同时使算优雅而容易实现。

首先,最“刺眼”的是第五步。在这一步中,需要转置超级图,还需要对转置的超级图进行一次深度优先搜索。能不能去掉这一步?也即是,如果 DAG 上存在惟一的 汇,则能不能有快速的办法判断该汇是否是最受欢迎的?现在,让我们来看本题中,最漂亮的结论。

事实五:如果有限 DAG只有一个汇,则该汇能够被其它的任一顶点所到达。

这个事实并不明显,因此需要简单证明一下。用反证法。假设原图 G 存在至少一个顶点 u,不能到达汇 t。把图上的所有顶点分成两类。第一类为包含 t 以及能够到达 t 的所有顶点;第二类不能够到达 t 的。显然,第一类至少有顶点 t,而第二类至少有顶点 u,所以均不为空。因此,第二类的所有顶点构成一个非空的子图 G',且该子图也是一个 DAG,从而由事实四,存在至少一个汇 s,即 s 没有边到第二类顶点中的任何顶点。我们断言,s 也是 G 的汇。这是,因为 s 不可能有边连到第一类中的任何顶点 v,否则 s->v-->t (s->v 表示有一条边从 s 到 v,而 v--> t 表示有一条路径从 v 到达 t )为一条路径从 s 到 t,这与 s 为第二类顶点相矛盾。因此,s 没有边指向 G 中的其它任何顶点,也即 s 是 G 的一个汇。但这与 G 只有一个汇相矛盾,因此,假设不成立,从而不存在顶点 u 不能到达汇 t,即 t 能够被任何其它顶点所到达。

这样,我们就可以把第五步省掉了。在剩下的四步中,最重要的就是一个强连通子图的出度是否为0。通过修改求强连通子图的 Tarjan 算法,可以在一遍的深度优先搜索中一起判断出求得的强连通子图出度是否为 0。这样,我们也就无需建立原图的超级图了,从而只需要一遍深度优先搜索即可以解决本题。这样,问题才算得到比较完美的解答。

algorithm 夜鱼 is
input: graph G = (V, E)
output: the number of popular cows
index := 0
S := empty
sink_count := 0 // the number of sinks
popular_count := undefined // the number of popular cows
for each v in V do
if (v.index is undefined AND popular_count is not zero)
strongconnect(v)
end if
repeat
output popular_count
procedure strongconnect(v)
// Set the depth index for v to the smallest unused index
v.index := index
v.lowlink := index
index := index + 1
S.push(v)
v.out_flag := false  // indicate if v is connected to other SCC
// Consider successors of v
for each (v, w) in E do
if (w.index is undefined AND popular_count is not zero) then
// Successor w has not yet been visited; recurse on it
strongconnect(w)
v.lowlink := min(v.lowlink, w.lowlink)
else if (w is in S) then
// Successor w is in stack S and hence in the current SCC
v.lowlink := min(v.lowlink, w.index)
else
v.out_flag := true
end if
repeat
// If v is a root node, pop the stack and generate an SCC
if (v.lowlink = v.index) then
v.scc_id := any unused scc id
scc_size := 0 // the size of this SCC
repeat
w := S.pop()
w.scc_id := v.scc_id
scc_size := scc_size + 1
v.out_flag := v.out_degree OR w.out_flag
until (w = v)
if v.out_flag = true then
sink_count := sink_count + 1
popular_count := scc_size
end if
end if
if sink_count > 1 then
popular_count := 0
end if
end procedure


总结:有些题就是要深入挖掘才能找到漂亮的解法,而一些数学理论和性质往往在深入挖掘的过程中发挥作用,本题就是一例。这类问题也是本人比较喜欢的一类:通过分析题目的一些性质和结合一些数学理论,从而找到优雅漂亮的解法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息