歪路:第一问找入度为 0 的点;第二问找出度为 0 的点。 反例:由一个环 A 传递到另一个环B,没有入度出度为 0 的点,但是至少要在环 A 传入信息。第二问,至少要建一条边 B 到 A 回传。
分析:环可以缩为一点,其内部可以相互传递,对于外部来说相当于一个点。得到一个新的有向无环图后,第一问即入度为 0 的点。假设存在 a 个起点(入度为 0),b 个终点(出度为 0),为使得信息可以传递,需引入 a 条边到起点,从终点引出 b 个终点。这些边的另一个端点是随意的(只要不是自身),但为了尽量减少新建的边,最好将边从终点引到起点,其余的再随便连。因此需要连 max(a,b) 条边。
voidTarjan(u){ dfn[u]=low[u]=++index stack.push(u) for each (u, v) in E { if (v is not visted) { tarjan(v) low[u] = min(low[u], low[v]) } elseif (v in stack) { low[u] = min(low[u], dfn[v]) //low[u] = min(low[u], low[v]) } } if (dfn[u] == low[u]) { //u是一个强连通分量的根 repeat v = stack.pop print v until (u == v) } //退栈,把整个强连通分量都弹出来 } //复杂度是O(E+V)的
若有一个子图的 low[i] 值相同,则这个子图即为一个强连通分量。可以考虑该子图中的任何一点j,设 low[j]=a=dfn[i],则 j 可以到达 i 点;同时 dfn[i]=a<=dfn[j],即 j 是经 i 点dfs得到的,则 i 可以到达 j 点。 i 点可以到达子图中的任意一点并返回至 i 点。因此该子图是强连通的。且有 low[i]=dfn[i]。
else if (v in stack) low[u] = min(low[u], dfn[v]) => low[u] = min(low[u], low[v])
low[i] 是点 i 可达点集的最小dfn,且 dfn[i] 和 i 是双射的,记 dfn−1[dfn[i]]=i。如果 low[v]=a,则说明v可以到达 dfn−1[a],而 u 又能到达 v,故 u 也能到达 dfn−1[a] 则 low[v]<low[u] 时,确实可以让 low[u] 取到 low[v]。而 dfn[v]>=low[v],不禁要怀疑原来的式子是不是不够紧? 想了想,其实这两种方法求出的强连通分量是相同的,只是过程有些不一样。给出下面 5 幅图,dfs 顺序为从上到下,从左到右。要保证 v 比 u 先入栈,且 v 可能比 u 的根更早入栈(图1,2),或更晚(图3,4,5),且 v 可能回溯成环(图2,4,5),实际上图3,4不会存在,因为 v 随着自己的强连通分量被弹出栈了!虽然 u 的 low 值会被带歪,但是可以保证的是,只有能囊括所有最大强连通分量的根才会出现 low 值等于 dfn 值,并提出该强连通分量。 如果哪天遇到反例了一定要告诉我啊~