【前言】
- 本文为博主的转载,由于博主看到的文章同样是转载的,无法注明原文出处。
- 博主在原文的基础上修改了格式、措辞和一些小错误,并适当添加了一些自己的理解。
【支配树简介】
对于一个单源有向图上的每个点 w w w,都存在点 d d d满足去掉 d d d之后起点无法到达 w w w,我们称作 d d d支配 w w w, d d d是 w w w的一个支配点。
支配 w w w的点可以有多个,但是至少会有一个。显然,对于起点以外的点,它们都有两个平凡的支配点,一个是自己,一个是起点。
在支配 w w w的点中,如果一个支配点 i ≠ w i \neq w i̸=w满足 i i i被 w w w剩下的所有支配点支配,则这个 i i i称作 w w w的最近支配点 ( i m m e d i a t e d o m i n a t o r ) (immediate\ dominator) (immediate dominator) ,记作 i d o m ( w ) idom(w) idom(w)。
定理 1 1 1 :我们把图的起点称作 r r r,除 r r r以外每个点均存在唯一的 i d o m idom idom。
证明: 如果 a a a支配 b b b且 b b b支配 c c c,则 a a a一定支配 c c c,因为到达 c c c的路径都经过了 b b b所以必须经过 a a a。如果 b b b支配 c c c且 a a a支配 c c c,则 a a a支配 b b b(或者 b b b支配 a a a),否则存在从 r r r到 b b b再到 c c c的路径绕过 a a a,与 a a a支配 c c c矛盾。这就意味着支配定义了点 w w w的支配点集合上的一个全序关系,所以一定可以找到一个“最小”的元素使得所有元素都支配它。
于是, i d o m ( w ) → w ( w ≠ r ) idom(w) \to w\ (w\ne r) idom(w)→w (w̸=r) 的边,就能得到一棵树,其中每个点支配它子树中的所有点,它就是支配树。
下文介绍了构建支配树的 L e n g a u e r − T a r j a n Lengauer-Tarjan Lengauer−Tarjan 算法,其时间复杂度为 O ( N L o g N + M ) O(NLogN+M) O(NLogN+M) ,空间复杂度为 O ( N + M ) O(N+M) O(N+M) 。
为了能够求出支配树,我们下面来推导一下需要用到的一些定理。
【定理推导】
首先,我们会使用一棵 D F S DFS DFS 树来帮助我们计算。从起点出发进行 D F S DFS DFS 就可以得到一棵 D F S DFS DFS 树。
原图中的边被分为了以下两类:在 D F S DFS DFS 树上出现的边称作树边,剩下的边称为非树边。非树边也可以分为几类:从祖先指向后代(前向边),从后代指向祖先(后向边),从一棵子树內指向另一棵子树内(横叉边)。树边是我们非常熟悉的,所以着重考虑一下非树边。
我们按照 D F S DFS DFS 到的先后顺序给点从小到大编号(在下面的内容中我们通过这个比较两个节点),那么前向边总是由编号小的指向编号大的,后向边总是由大指向小,横叉边也总是由大指向小。现在在 D F S DFS DFS 树上我们要证明一些重要的引理:
引理 1 1 1 (路径引理):如果两个点 v , w v,w v,w满足 v ≤ w v \leq w v≤w,那么任意 v v v到 w w w的路径经过 v , w v,w v,w的公共祖先。(注意这里不是说 L C A LCA LCA )
证明: 如果 v , w v,w v,w其中一个是另一个的祖先显然成立。否则删掉起点到 L C A LCA LCA 路径上的所有点(这些点是 v , w v,w v,w的公共祖先),那么 v v v和 w w w在两棵子树内,并且因为公共祖先被删去,无法通过后向边到达子树外面,前向边也无法跨越子树,而横叉边只能从大到小,所以从 v v v出发不能离开这颗子树到达 w w w。所以如果本来 v v v能够到达 w w w,就说明这些路径必须经过 v , w v,w v,w的公共祖先。
在继续之前,我们先约定一些记号:
V V V代表图的点集, E E E代表图的边集。 a → b a \to b a→b代表从点 a a a直接经过一条边到达点 b b b, a ⇝ b a \leadsto b a⇝b代表从点 a a a经过某条路径到达点 b b b, a → ˙ b a \dot \to b a→˙b代表从点 a a a经过 D F S DFS DFS 树上的树边到达点 b b b( a a a是 b b b在 D F S DFS DFS 树上的祖先), a → + b a \overset{+}{\to} b a→+b代表 a → ˙ b a \dot \to b a→˙b且 a ≠ b a \neq b a̸=b。
定义:半支配点 ( s e m i − d o m i n a t o r ) (semi-dominator) (semi−dominator) :对于 w ≠ r w \neq r w̸=r,它的半支配点定义为 s d o m ( w ) = min { v ∣ ∃ ( v 0 , v 1 , ⋯   , v k − 1 , v k ) , v 0 = v , v k = w , ∀ 1 ≤ i ≤ k − 1 , v i > w } sdom(w)=\min\{ v\ |\ \exists (v_0,v_ 1 ,\cdots,v_{k- 1 },v_k), v_0 = v, v_k = w, \forall 1 \leq i \leq k- 1 , v_i>w \} sdom(w)=min{v ∣ ∃(v0,v1,⋯,vk−1,vk),v0=v,vk=w,∀1≤i≤k−1,vi>w}
这个定义可以理解为从 v v v出发,可以绕过小于 w w w的所有点到达 w w w(只能以大于 w w w 的点作为落脚点)的最小的 v v v 。
注意这只是个辅助定义,并不是真正的支配点。
引理 2 2 2 :对于任意 w ≠ r w \neq r w̸=r,有 i d o m ( w ) → + w idom(w) \overset{+}{\to} w idom(w)→+w。
证明: 如果不是这样的话就可以直接通过树边不经过 i d o m ( w ) idom(w) idom(w)就到达 w w w了,与 i d o m idom idom定义矛盾。
引理 3 3 3 :对于任意 w ≠ r w \neq r w̸=r,有 s d o m ( w ) → + w sdom(w) \overset{+}{\to} w sdom(w)→+w。
证明: 对于 w w w在 D F S DFS DFS 树上的父亲 f a w fa_w faw, f a w → w fa_w \to w faw→w这条路径只有两个点,所以满足 s d o m sdom sdom定义中的条件,于是它是 s d o m ( w ) sdom(w) sdom(w)的一个候选。所以 s d o m ( w ) ≤ f a w sdom(w) \leq fa_w sdom(w)≤faw。在这里我们就可以使用路径引理证明 s d o m ( w ) sdom(w) sdom(w)不可能在另一棵子树,因为如果是那样的话就会经过 s d o m ( w ) sdom(w) sdom(w)和 w w w的一个公共祖先,公共祖先的编号一定小于 w w w,所以不可行。于是 s d o m ( w ) sdom(w) sdom(w)就是 w w w的真祖先。
引理 4 4 4 :对于任意 w ≠ r w \neq r w̸=r,有 i d o m ( w ) → ˙ s d o m ( w ) idom(w) \dot \to sdom(w) idom(w)→˙sdom(w)。
证明: 如果不是这样的话,按照 s d o m sdom sdom的定义,就会有一条路径是 r → ˙ s d o m ( w ) ⇝ w r \dot \to sdom(w) \leadsto w r→˙sdom(w)⇝w不经过 i d o m ( w ) idom(w) idom(w)了,与 i d o m idom idom定义矛盾。
引理 5 5 5 :对于满足 v → ˙ w v \dot \to w v→˙w的点 v , w v,w v,w, v → ˙ i d o m ( w ) v \dot \to idom(w) v→˙idom(w)或 i d o m ( w ) → ˙ i d o m ( v ) idom(w) \dot \to idom(v) idom(w)→˙idom(v)。
直观地理解就是 i d o m ( w ) idom(w) idom(w)到 w w w的路径两两之间边不相交或者存在完全包含关系。
证明: 如果不是这样的话,就说明 i d o m ( v ) → + i d o m ( w ) → + v → + w idom(v) \overset{+}{\to} idom(w) \overset{+}{\to} v \overset{+}{\to} w idom(v)→+idom(w)→+v→+w,那么存在路径 r → ˙ i d o m ( v ) ⇝ v → + r \dot \to idom(v) \leadsto v \overset{+}{\to} r→˙idom(v)⇝v→+不经过 i d o m ( w ) idom(w) idom(w)到达了 w w w(因为 i d o m ( w ) idom(w) idom(w)是 i d o m ( v ) idom(v) idom(v)的真后代,一定不支配 v v v,所以存在绕过 i d o m ( w ) idom(w) idom(w)到达 v v v的路径),矛盾。
上面这 5 5 5 条引理都比较简单,但是非常重要的性质。接下来我们要证明几个定理,它们揭示了 i d o m idom idom与 s d o m sdom sdom的关系。证明会比上面的复杂一点。
定理 2 2 2 :对于任意 w ≠ r w \neq r w̸=r,如果所有满足 s d o m ( w ) → + u → ˙ w sdom(w) \overset{+}{\to} u \dot \to w sdom(w)→+u→˙w的 u u u也满足 s d o m ( u ) ≥ s d o m ( w ) sdom(u) \geq sdom(w) sdom(u)≥sdom(w),那么 i d o m ( w ) = s d o m ( w ) idom(w) = sdom(w) idom(w)=sdom(w)。
证明: 条件可以写为 s d o m ( w ) → ˙ s d o m ( u ) → + u → ˙ w sdom(w) \dot \to sdom(u) \overset{+}{\to} u \dot \to w sdom(w)→˙sdom(u)→+u→˙w。
由上面的引理 4 4 4 知道 i d o m ( w ) → ˙ s d o m ( w ) idom(w) \dot \to sdom(w) idom(w)→˙sdom(w),所以只要证明 s d o m ( w ) sdom(w) sdom(w)支配 w w w就可以保证是最近支配点了。对任意 r r r到 w w w的路径,取上面最后一个编号小于 s d o m ( w ) sdom(w) sdom(w)的 x x x(如果 s d o m sdom sdom就是 r r r的话显然定理成立),它必然有个后继 y y y满足 s d o m ( w ) → ˙ y → ˙ w sdom(w) \dot \to y \dot \to w sdom(w)→˙y→˙w(否则 x x x会变成 s d o m ( w ) sdom(w) sdom(w)),我们取最小的那个 y y y。同时,如果 y y y不是 s d o m ( w ) sdom(w) sdom(w),根据条件, s d o m ( y ) ≥ s d o m ( w ) sdom(y) \geq sdom(w) sdom(y)≥sdom(w),所以 x x x不可能是 s d o m ( y ) sdom(y) sdom(y),这就意味着 x x x到 y y y的路径上一定有一个 v v v满足 x → + v → + y x \overset{+}{\to} v \overset{+}{\to} y x→+v→+y,因为 x x x是小于 s d o m ( w ) sdom(w) sdom(w)的最后一个,所以 v v v也满足 s d o m ( w ) → ˙ v → ˙ w sdom(w) \dot \to v \dot \to w sdom(w)→˙v→˙w,但是我们取的 y y y已经是最小的一个了,矛盾。于是 y y y只能是 s d o m ( w ) sdom(w) sdom(w),那么我们就证明了对于任意路径都要经过 s d o m ( w ) sdom(w) sdom(w),所以 s d o m ( w ) sdom(w) sdom(w)就是 i d o m ( w ) idom(w) idom(w)。
定理 3 3 3 :对于任意 w ≠ r w \neq r w̸=r,令 u u u为所有满足 s d o m ( w ) → + u → ˙ w sdom(w) \overset{+}{\to} u \dot \to w sdom(w)→+u→˙w的 u u u中 s d o m ( u ) sdom(u) sdom(u)最小的一个,那么 s d o m ( u ) ≤ s d o m ( w ) ⇒ i d o m ( w ) = i d o m ( u ) sdom(u) \leq sdom(w) \Rightarrow idom(w) = idom(u) sdom(u)≤sdom(w)⇒idom(w)=idom(u)。
证明: 条件可以写为 s d o m ( u ) → ˙ s d o m ( w ) → + u → ˙ w sdom(u) \dot \to sdom(w) \overset{+}{\to} u \dot \to w sdom(u)→˙sdom(w)→+u→˙w。
由引理 5 5 5 ,有 i d o m ( w ) → ˙ i d o m ( u ) idom(w) \dot \to idom(u) idom(w)→˙idom(u)或 u → ˙ i d o m ( w ) u \dot \to idom(w) u→˙idom(w),由引理 4 4 4 排除后面这种。所以只要证明 i d o m ( u ) idom(u) idom(u)支配 w w w即可。类似定理 2 2 2 的证明,我们取任意 r r r到 w w w路径上最后一个小于 i d o m ( u ) idom(u) idom(u)的 x x x(如果 i d o m ( u ) idom(u) idom(u)是 r r r的话显然定理成立),路径上必然有个后继 y y y满足 i d o m ( u ) → ˙ y → ˙ w idom(u) \dot \to y \dot \to w idom(u)→˙y→˙w(否则 x x x会变成 s d o m ( w ) sdom(w) sdom(w)),我们取最小的一个 y y y。类似上面的证明,我们知道 x x x到 y y y的路径上不能有点 v v v满足 i d o m ( u ) → ˙ v → + y idom(u) \dot \to v \overset{+}{\to} y idom(u)→˙v→+y,于是 x x x成为 s d o m ( y ) sdom(y) sdom(y)的候选,所以 s d o m ( y ) ≤ x sdom(y) \leq x sdom(y)≤x。那么根据条件我们也知道了 y y y不能是 s d o m ( w ) sdom(w) sdom(w)的真后代,于是 y y y满足 i d o m ( u ) → ˙ y → ˙ s d o m ( w ) idom(u) \dot \to y \dot \to sdom(w) idom(u)→˙y→˙sdom(w)。但是我们注意到因为 s d o m ( y ) ≤ x sdom(y) \leq x sdom(y)≤x,存在一条路径 r → ˙ s d o m ( y ) ⇝ y → ˙ u r \dot \to sdom(y) \leadsto y \dot \to u r→˙sdom(y)⇝y→˙u,如果 y y y不是 i d o m ( u ) idom(u) idom(u)的话这就是一条绕过 i d o m ( u ) idom(u) idom(u)的到 u u u的路径,矛盾,所以 y y y必定是 i d o m ( u ) idom(u) idom(u)。所以任意到 w w w的路径都经过 i d o m ( u ) idom(u) idom(u),所以 i d o m ( w ) = i d o m ( u ) idom(w)=idom(u) idom(w)=idom(u) 。
完成了上面两个定理的证明,我们就能够通过 s d o m sdom sdom求出 i d o m idom idom了。
推论 1 1 1 :对于 w ≠ r w \neq r w̸=r,令 u u u为所有满足 s d o m ( w ) → + u → ˙ w sdom(w) \overset{+}{\to} u \dot \to w sdom(w)→+u→˙w的 u u u中 s d o m ( u ) sdom(u) sdom(u)最小的一个,有
i d o m ( w ) = { s d o m ( w ) ( s d o m ( u ) = s d o m ( w ) ) i d o m ( u ) ( s d o m ( u ) < s d o m ( w ) ) idom(w) = \left \{ \begin{aligned}& sdom(w)&(sdom(u)=sdom(w))&\\ &idom(u)&(sdom(u)<sdom(w))&\end{aligned} \right . idom(w)={sdom(w)idom(u)(sdom(u)=sdom(w))(sdom(u)<sdom(w))
推论 1 1 1 可以通过定理 2 2 2 和定理 3 3 3 可以直接得到。这里一定有 s d o m ( u ) ≤ s d o m ( w ) sdom(u) \leq sdom(w) sdom(u)≤sdom(w),因为 w w w也是 u u u的候选。
接下来我们的问题是,直接通过定义计算 s d o m sdom sdom很低效,我们需要更加高效的方法,所以我们证明下面这个定理:
定理 4 4 4 :对于任意 w ≠ r w \neq r w̸=r
s d o m ( w ) = m i n ( v ∣ ( v , w ) ∈ E , v < w ∪ s d o m ( u ) ∣ u > w , ∃ ( v , w ) ∈ E , u → ˙ v ) sdom(w) = min({v\ |\ (v, w) \in E , v < w} \cup {sdom(u)\ |\ u > w , \exists (v, w) \in E , u \dot \to v} ) sdom(w)=min(v ∣ (v,w)∈E,v<w∪sdom(u) ∣ u>w,∃(v,w)∈E,u→˙v)
证明: 令右侧为 x x x,显然右侧的点集中都存在路径绕过 w w w之前的点,所以 s d o m ( w ) ≤ x sdom(w) \leq x sdom(w)≤x。然后我们考虑 s d o m ( w ) sdom(w) sdom(w)到 w w w的绕过 w w w之前的点的路径,如果只有一条边,那么必定满足 ( s d o m ( w ) , w ) ∈ E (sdom(w),w) \in E (sdom(w),w)∈E且 s d o m ( w ) < w sdom(w)<w sdom(w)<w,所以此时 x ≤ s d o m ( w ) x \leq sdom(w) x≤sdom(w);如果多于一条边,令路径上 w w w的上一个点为 l a s t last last,我们取路径上除两端外满足 p → ˙ l a s t p \dot \to last p→˙last的最小的 p p p(一定能取得这样的 p p p,因为 l a s t last last是 p p p的候选)。因为这个 p p p是最小的,所以 s d o m ( w ) sdom(w) sdom(w)到 p p p的路径必定绕过了 p p p之前的所有点,于是 s d o m ( w ) sdom(w) sdom(w)是 s d o m ( p ) sdom(p) sdom(p)的候选,所以 s d o m ( p ) ≤ s d o m ( w ) sdom(p) \leq sdom(w) sdom(p)≤sdom(w)。同时, s d o m ( p ) sdom(p) sdom(p)还满足右侧的条件( p p p在绕过 w w w之前的点的路径上,于是 p > w p>w p>w,并且 p → ˙ l a s t p\dot \to last p→˙last,同时 l a s t last last直接连到了 w w w),所以 s d o m ( p ) sdom(p) sdom(p)是 x x x的候选, x ≤ s d o m ( p ) x \leq sdom(p) x≤sdom(p)。所以 x ≤ s d o m ( p ) ≤ s d o m ( w ) x \leq sdom(p) \leq sdom(w) x≤sdom(p)≤sdom(w), x ≤ s d o m ( w ) x \leq sdom(w) x≤sdom(w)。综上, s d o m ( w ) ≤ x sdom(w) \leq x sdom(w)≤x且 x ≤ s d o m ( w ) x \leq sdom(w) x≤sdom(w),所以 x = s d o m ( w ) x=sdom(w) x=sdom(w)。
现在最困难的步骤已经完成了,我们得到了 s d o m sdom sdom的一个替代定义,而且这个定义里面的形式要简单得多。这种基本的树上操作我们是非常熟悉的,所以没有什么好担心的了。接下来就可以给出我们需要的算法了。
【构造流程】
以下是算法的简要流程
1 1 1 、初始化、跑一遍 D F S DFS DFS 得到 D F S DFS DFS 树和标号
2 2 2 、按标号从大到小求出 s d o m sdom sdom(利用定理 4 4 4 )
3 3 3 、通过推论 1 1 1 求出所有能确定的 i d o m idom idom,剩下的点记录下和哪个点的 i d o m idom idom是相同的
4 4 4 、按照标号从小到大再跑一次,得到所有点的 i d o m idom idom以下是算法的具体实现细节
大致要维护的东西:
p ( x ) p(x) p(x) 标号为 x x x的点 u u u
b ( u ) b(u) b(u) 有边直接连到 u u u的点集
c ( u ) c(u) c(u) s d o m sdom sdom为点 u u u的点集
f a t h e r ( u ) father(u) father(u) u u u在 D F S DFS DFS 树上的父亲 f a u fa_u fau
以及 i d o m idom idom和 s d o m sdom sdom数组这里多说一句,由于我们上文的推导中我们经常会用一个点的 D F S DFS DFS 序来代替一个点来论述,我们有时会无法分清一个数组的下标和数值究竟代表一个点 x x x 在原图中的标号还是它的 D F S DFS DFS 序。
为了统一,我们不妨规定数组的下标一律使用点在原图中的标号,除了计算过程中的 i d o m idom idom和 s d o m sdom sdom数组记录点的 D F S DFS DFS 序标号以外,其余数组一律记录点在原图中的标号。
算法的第 1 1 1 步没什么特别的,规规矩矩地 D F S DFS DFS 一次即可,同时初始化 s d o m sdom sdom为自己(这是为了实现方便)。
第 2 2 2 、第 3 3 3 步可以一起做。通过一个辅助数据结构维护一个森林,支持加入一条边 ( l i n k ( u , v ) ) (link(u,v) ) (link(u,v)) 和查询点到根路径上的点的 s d o m sdom sdom的最小值对应的点 ( h o m e ( u ) ) ( home(u) ) (home(u)) 。那么我们求每个点的 s d o m sdom sdom只需要对它的所有直接前驱 h o m e home home一次,求得前驱中的 s d o m sdom sdom最小值即可。因为定理 4 4 4 中的第一类点编号比它小,它们还没有处理过,所以自己就是根, h o m e home home就能取得它们的值;对于第二类点, h o m e home home查询的就是满足 u → ˙ v u \dot \to v u→˙v的 u u u的 s d o m ( u ) sdom(u) sdom(u)的最小值。所以这么做和定理 4 4 4 是一致的。
然后把该点加入它的 s d o m sdom sdom的 c c c里,连上它与父亲的边。现在它父亲到它的这棵子树中已经处理完了,所以可以对父亲的 c c c里的每个点求一次 i d o m idom idom并且清空 c c c。对于 c c c里的每个点 v v v,求出 h o m e ( v ) home(v) home(v),此时 f a t h e r ( w ) → + h o m e ( v ) → ˙ v father(w) \overset{+}{\to} home(v) \dot \to v father(w)→+home(v)→˙v,于是直接按照推论 1 1 1 ,如果 s d o m ( h o m e ( v ) ) = s d o m ( v ) sdom(home(v))=sdom(v) sdom(home(v))=sdom(v),则 i d o m ( v ) = s d o m ( v ) = f a t h e r ( w ) idom(v)=sdom(v)=father(w) idom(v)=sdom(v)=father(w);否则可以记下 i d o m ( v ) = i d o m ( h o m e ( v ) ) idom(v)=idom(home(v)) idom(v)=idom(home(v)),实现时我们可以写成 i d o m ( v ) = h o m e ( v ) idom(v)=home(v) idom(v)=home(v),留到第 4 4 4 步处理。
最后从小到大扫一遍完成第 4 4 4 步,对于每个 u u u,如果 i d o m ( u ) = s d o m ( u ) idom(u)=sdom(u) idom(u)=sdom(u)的话,就已经是第 3 3 3 步求出的正确的 i d o m idom idom了,否则就证明这是第 3 3 3 步留下的待处理点,令 i d o m ( u ) = i d o m ( i d o m ( u ) ) idom(u)=idom(idom(u)) idom(u)=idom(idom(u))即可。
至于这个辅助数据结构,我们可以选择并查集。不过因为我们需要查询到根路径上的信息,所以不方便按秩合并,但是我们仍然可以路径压缩,压缩时保留路径上的最值就可以了。这样做的话,最终的时间复杂度是 O ( N L o g N + M ) O(NLogN+M) O(NLogN+M)。
【代码】
- 模板题【HDU4694】
#include
using namespace std; const int MAXN = 50005; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } int n, m, timer, root, dfn[MAXN], p[MAXN], father[MAXN]; int idom[MAXN], sdom[MAXN], f[MAXN], home[MAXN]; long long ans[MAXN]; vector <int> a[MAXN], b[MAXN], c[MAXN]; void dfs(int pos) { dfn[pos] = ++timer, p[timer] = pos; for (unsigned i = 0; i < a[pos].size(); i++) if (dfn[a[pos][i]] == 0) { father[a[pos][i]] = pos; dfs(a[pos][i]); } } int F(int x) { if (f[x] == x) return x; int tmp = f[x]; f[x] = F(f[x]); if (sdom[home[tmp]] < sdom[home[x]]) home[x] = home[tmp]; return f[x]; } int gethome(int x) { F(x); return home[x]; } void work(int pos, long long sum) { ans[pos] = sum; for (unsigned i = 0; i < a[pos].size(); i++) work(a[pos][i], sum + a[pos][i]); } int main() { while (scanf("%d%d", &n, &m) != EOF) { for (int i = 1; i <= n; i++) { a[i].clear(); b[i].clear(); c[i].clear(); } for (int i = 1; i <= m; i++) { int x, y; read(x), read(y); a[x].push_back(y); b[y].push_back(x); } memset(dfn, 0, sizeof(dfn)); timer = 0; dfs(root = n); for (int i = 1; i <= timer; i++) { sdom[p[i]] = i; idom[p[i]] = 0; f[p[i]] = home[p[i]] = p[i]; } for (int i = timer; i >= 2; i--) { int tmp = p[i]; for (unsigned j = 0; j < b[tmp].size(); j++) if (dfn[b[tmp][j]]) chkmin(sdom[tmp], sdom[gethome(b[tmp][j])]); c[sdom[tmp]].push_back(tmp); f[tmp] = father[tmp]; tmp = dfn[father[tmp]]; for (unsigned j = 0; j < c[tmp].size(); j++) { int tnp = gethome(c[tmp][j]); if (sdom[tnp] == tmp) idom[c[tmp][j]] = tmp; else idom[c[tmp][j]] = dfn[tnp]; } c[tmp].clear(); } for (int i = 1; i <= n; i++) a[i].clear(); for (int i = 2; i <= timer; i++) { int tmp = p[i]; if (sdom[tmp] == idom[tmp]) idom[tmp] = p[idom[tmp]]; else idom[tmp] = idom[p[idom[tmp]]]; sdom[tmp] = p[sdom[tmp]]; a[idom[tmp]].push_back(tmp); } memset(ans, 0, sizeof(ans)); work(root, root); for (int i = 1; i <= n; i++) { write(ans[i]); if (i == n) putchar('\n'); else putchar(' '); } } return 0; }