别人都在复习$NOIP$,我也不知道干什么,就来学学一些诡异的算法吧...
首先,假设我们有一张有向图$G(V, E)$,并且选定一个点$s$为根
我们定义$x$支配$y$,当且仅当从$G$中删除$x$时,没有从$s$到$y$的路径
为了简便描述,我们找出一棵以$s$为根的$dfs$树$T$
性质1:易知支配$x$的一定是个集合$S$,并且$S$在$T$中一定都是$x$的祖先
如果不是,那么至少树边就是一种到$x$的方案
我们定义$dom(x)$表示支配$x$的点形成的集合
我们定义$idom(x)$(最近支配点)为$dom(x)$中$dfn$最大的节点,也就是$dom(x)$中离$x$最近的祖先
性质2:如果我们不断的访问$idom(x), idom(idom(x))...$,那么我们一定能访问完$dom(x)$
换言之,连边$idom(x) o x$就会构成了一棵树,并且这个树中$x$的所有祖先节点都是支配$x$的点
证明:我们考虑证明如果$x$支配$u$,并且$y$支配$u$,那么一定有$x$支配$y$或者$y$支配$x$
由性质1,我们假定$x$是$y$的祖先,如果$x$不支配$y$,那么删除$x$后仍有到$y$的路径,自然也有到$u$的路线了
这与$x$支配$u$矛盾(链形如:$r o x o y o x o u$)
我们称这棵树为支配树
只要考虑求出所有的$idom$,就能求出这棵支配树
注意到$T$的树边是一个十分强大的限制
因此,如果$x$存在一条(不经过$x$和$v$之间的树边)到$v$的边,那么$x$的所有子树都不可能支配$v$
我们取深度最浅的($dfn$最小)的满足条件的$x$
我们令$x = semi(v)$,称$x$为$v$的半支配点
注意半支配点不一定是支配点,比如:
$y$是$x$的半支配点,$r$是$x$的支配点
我们考虑求出看起来十分有用的半支配点
对于一个点$x$,有$(y, x)$的前向边的$y$可能成为答案
不仅如此,如果有$(y, x)$这条返祖边,那么$x o y$的路径中的$semi(i)$都可能成为答案
同样的道理,$(y, x)$作为交错边时也需要考虑
考虑以下的定理
我们令$semi(x)$到$x$路径中的所有点$i$中,$semi(i)$最小的$i$为$y$
$$idom(x) =
egin{cases}
& if ; ; (semi(x) = semi(y)); ; ; semi(x) \
& else ; ; ; idom(y) \
end{cases}$$
证明:
首先证明第一条
1. 在删去$semi(x)$后,不存在$s$到$x$的路径
反证,如果存在这条路径,如果是经过了$y$,那么$semi(y)$会更小,否则$semi(x)$会更小
2.删去$semi(x)$的任意子树($x$的祖先),都会存在$s$到$x$的路径
显然,存在一条$semi(x)$不经过树边到$x$的路径,无法断掉
类似地,证明第二条
如果懒得画图,那么可以参考路径$s o idom(y) o semi(y) o semi(x) o y o x$
1.在删去$idom(y)$后,不存在$s$到$x$的路径
反证,如果存在,如果经过了$y$,那么$idom(y)$不符合条件,如果不经过$y$,那么要么$y$不符合条件,要么$semi(x)$不符合条件
2.删除$idom(y)$的任意子树($x$的祖先),仍会存在$s$到$x$的路径
自然的,存在一条到$y$的路径,那么一定可以到达$semi(x)$,也就一定能到达$x$
现在,我们可以求出$idom(i)$和$semi(i)$了
我们使用带权并查集维护每个点到当前节点的链的$semi$最小的节点
按$dfn$倒叙处理(这是为了防止交错边)
注意并查集合并时,由于取$min$不满足可加可减性,不能使用按秩合并
复杂度$O(n log n)$
int n, cnp, tim; int f[sid], fa[sid], dfn[sid]; int idom[sid], semi[sid], ord[sid], mi[sid]; int cap[sid], q1[sid], q2[sid], nxt[sid * 3], node[sid * 3]; inline void addedge(int *head, int u, int v) { nxt[++ cnp] = head[u]; head[u] = cnp; node[cnp] = v; } inline bool cmp(int a, int b) { return dfn[a] < dfn[b]; } inline int find(int u) { if(u == f[u]) return u; int v = find(f[u]); if(cmp(semi[mi[f[u]]], semi[mi[u]])) mi[u] = mi[f[u]]; return f[u] = v; } #define cur node[i] inline void dfs(int u) { dfn[u] = ++ tim; ord[tim] = u; for(int i = cap[u]; i; i = nxt[i]) if(!dfn[cur]) dfs(cur), fa[cur] = u; } inline void tarjan() { rep(i, 1, n) idom[i] = semi[i] = mi[i] = f[i] = i; drep(i, tim, 2) { int o = ord[i]; for(int i = q1[o]; i; i = nxt[i]) if(dfn[cur]) {
//q1是反向边 find(cur); if(cmp(semi[mi[cur]], semi[o])) semi[o] = semi[mi[cur]]; } f[o] = fa[o]; addedge(q2, semi[o], o); for(int i = q2[o]; i; i = nxt[i]) { find(cur); idom[cur] = cmp(semi[mi[cur]], o) ? mi[cur] : o; //idom在这里存的是semi(y) } } rep(i, 2, tim) if(idom[i] != semi[i]) idom[i] = idom[idom[i]]; }
ps:如果模板错误,请及时提醒我,会速度更正