根据定理 1,我们可以得到一个 O(nlogn) 求半支配点的方法:按 dfs 序从后往前做,对于每个点 u 和他的前驱 v,若 v<u 则用 v 更新 sdom(u),否则用其 sdom 最小的祖先的 sdom 更新 sdom(u),后面这部分相当于询问从 v 开始往上的一条树链的 sdom 的最小值,使用带权并查集维护,每扫完一个点就将他与父亲合并即可。
求支配点
方法一:
在 T 基础上加上所有 sdom(u)→u 的边会形成一个 DAG,其上的支配关系与原图相同,因此在这个图上跑 DAG 的做法即可得到支配树。
证明:显然我们只需要考虑有祖先关系的点。首先容易发现原图存在的支配关系依然存在,然后要证明原图不存在的支配关系在新图上也不存在。
对于一个点 u 及其子树内的一个点 v,如果 u 不支配 v,那么存在一条路径不经过 u 进入 u 的子树内。我们考虑这条路径进入 u 子树前最后一个点 k 和进入后最小的到达的 u 和 v 中间这条树链中的点 r(包括 v 但不包括 u),LCA(u,k)⇝Tk→r 是一条满足 r 的半支配点的路径(可以证明 k⇝r 中的点都 >r),因此如果我们把所有满足半支配点条件的点对作为边全都加入那么支配关系就与原图相同了。而因为一个点所有满足半支配点条件的点都是他的祖先,所以我们只需要保留最上面的一个即可。□
其实这个做法很好写,因为每个点只会有两个入度而且在 T 上都是他的祖先,所以实际上也不需要拓扑排序直接按 dfs 序做也是对的。
方法二:
但是有人认为上面这个做法太不优雅了,所以这里给出另一种做法。
定理 2:对于任意节点 u,设 v 是 sdom(u) 到 u 这条链上 sdom 最小的点(不包括 u 和 sdom(u)),若 sdom(v)≥sdom(u),则 idom(u)=sdom(u)。
证明:等价于证 sdom(u)domu。
对于 sdom(u)=s 显然成立,下面考虑 sdom(u)=s 的情况。
假设存在一条到 u 的路径不经过 sdom(u),记其中最后一个小于 sdom(u) 的点为 x。
我们把树切成五部分,分别是 <sdom(u) 的点(记为第一部分),sdom(u),u,>sdom(u) 的点(记为第三部分)以及中间的部分(记为第二部分)。显然 x 在第一部分,且 x 要走到第一部分以外的地方只能通过树边或前向边,因此 x 一定是 sdom(u) 的祖先。
考虑 x 之后的路径,一定存在一个 y 使得其在 sdom(u) 到 u 的树链上,因为不存在这样的 y 等价于 x 之后没有进入第二部分,使得 x 满足半支配点的条件且 <sdom(u),那 sdom(u) 就会变成 x。
我们找到最小的 y,由于 sdom(y)≥sdom(u)>x,因此从 x 出发到 y 一定要经过 x 到 y 中间这条树链上的点。又因为 x 是最后一个小于 sdom(u) 的点,所以中间这个点一定在 sdom(u) 到 y 中间这条树链上,而这与 y 是最小的在 sdom(u) 到 u 中的点矛盾,因此不存在这样的路径,因此 sdom(u)domu。□
定理 3:对于任意节点 u,设 v 是 sdom(u) 到 u 这条链上 sdom 最小的点(不包括 u 和 sdom(u)),若 sdom(v)<sdom(u),则 idom(u)=idom(v)。
类似地,假设存在一条到 u 的路径不经过 idom(v),记其中最后一个小于 idom(v) 的点为 x。
同样的分析方法可以得到 x 是 idom(v) 的祖先,在路径上 x 之后一定存在一个 y 使得其在 idom(v) 到 u 的树链上。找到最小的 y。
可以发现这条路径上 x⇝y 是一条满足 y 的半支配点的条件的路径,因此 sdom(y)≤x<idom(v)≤sdom(v)。并且 y 一定不是 sdom(u) 的后代(否则由于 sdom(y)<sdom(v),与 sdom(v) 最小这点矛盾)因此 idom(v)<y≤sdom(u)。
int n, m; // n, m 为点数和边数 structgraph { int tot, hd[200005]; int nxt[300005], to[300005]; voidadd(int u, int v){ nxt[++tot] = hd[u]; hd[u] = tot; to[tot] = v; return ; } } g, fg; // g 为原图,fg 为反图 int timer, fa[200005], dfn[200005], id[200005]; int sdom[200005], idom[200005]; structdsu { int fa[200005], mn[200005];
dsu() {for (int i = 1; i < 200005; i++) fa[i] = mn[i] = i;} intfind(int x){ if (x == fa[x]) return x; int tmp = find(fa[x]); if (dfn[sdom[mn[fa[x]]]] < dfn[sdom[mn[x]]]) mn[x] = mn[fa[x]]; return fa[x] = tmp; } } d; vector<int> vec[200005];
voiddfs(int now){ id[dfn[now] = ++timer] = now; for (int i = g.hd[now]; i; i = g.nxt[i]) if (!dfn[g.to[i]]) fa[g.to[i]] = now, dfs(g.to[i]); return ; } voidsolve(){ dfs(1); for (int i = 1; i <= n; i++) sdom[i] = i; for (int i = timer; i >= 1; i--) { int u = id[i]; for (int v : vec[u]) { d.find(v); if (sdom[d.mn[v]] == u) idom[v] = u; else idom[v] = d.mn[v]; // 这里是一个省数组的写法,将 idom 和并查集用同一个数组维护了。 } if (i == 1) continue; for (int j = fg.hd[u]; j; j = fg.nxt[j]) { if (!dfn[fg.to[j]]) continue; if (dfn[fg.to[j]] < dfn[sdom[u]]) sdom[u] = fg.to[j]; elseif (dfn[fg.to[j]] > dfn[u]) { d.find(fg.to[j]); if (dfn[sdom[d.mn[fg.to[j]]]] < dfn[sdom[u]]) sdom[u] = sdom[d.mn[fg.to[j]]]; } } vec[sdom[u]].push_back(u); d.fa[u] = fa[u]; } for (int i = 2; i <= timer; i++) if (idom[id[i]] != sdom[id[i]]) idom[id[i]] = idom[idom[id[i]]]; return ; }