有趣的支配树

别人都在复习$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:如果模板错误,请及时提醒我,会速度更正

原文地址:https://www.cnblogs.com/reverymoon/p/9763144.html