支配树学习笔记
本文参考自cz_xuyixuan的支配树blog
以下给出若干定义
- 给定一张有向图,定义起点为 \(r\)
- 定义 \(x\) 是 \(y\) 的支配点,即 \(x \in dom(y)\),当且仅当删掉 \(x\) 后, 从 \(r\) 无法到达 \(y\),且 \(x != y\)
- 定义 \(x\) 是 \(y\) 的最近支配点,当且仅当,\(x \in dom(y)\) 且 \(\forall w \in dom(y),w != x\) 有 \(w \in dom(x)\),以下称为 \(idom(y) = x\),亦称为 \(x\) 是 \(y\) 的最近支配点
- 由定义可知,\(idom(x)\) 和 \(x\) 的连边可以构成一棵树,称为支配树
- 定义有向图的 \(dfs\) 树为 \(T\)
- 以下所有大小关系,均指 \(dfn\) 的大小关系,即重定义 \(<\) 表示 \(dfn[x] < dfn[y].\)
- 定义 \(x\) 是 \(y\) 的半支配点,即\(sdom(y) = \min\{x|\exist x -> v_1 -> v_2 -> ..... v_k ->y,\forall i \in [1,k],v_i > y\}\),用人话讲就是 \(x\) 为满足存在一条除首尾外,经过的点都大于 \(y\) 的到达 \(y\) 的路径的点中 \(dfs\) 序最小的点
- 因为起点 \(r\) 的支配点不存在,所以以下讨论都默认不讨论 \(r\)
- 以下用 \(a \rightarrow b\)表示存在一条 \(a\) 到 \(b\) 的路径,不经过树边,\(a -> b\)表示只经过树边, \(a\) 到 \(b\) 的所有路径则统称为 \((a,b) \in path\)
以下给出若干定理和引理
- \(idom(u)\) 一定是 \(u\) 的祖先(1)
显然
- \(idom(u)\)一定是 \(sdom(u)\) 的祖先(2)
\(proof\) :考虑反证,若不是,则存在 \(r -> sdom(u) \rightarrow u\)不经过 \(idom(u)\),矛盾
- \(sdom(u)\) 一定是 \(u\) 的祖先(3)
\(proof\) : 考虑反证,首先 \(sdom(u)\) 可以是 \(fa(u)\) ,所以 \(sdom(u) \leq fa(u) < u\) ,若不是祖先,则必然是其他子树,考虑在 \(dfs\) 的过程中 ,若 \((sdom(u),u) \in path\), 那么在遍历到 \(sdom(u)\) 的过程中,必然直接遍历到 \(u\) ,与 \(sdom(u) \rightarrow u\)矛盾
定理 ( idom 与 sdom 关系定理)
定义: \(u\) 为 \(sdom(w)\) 到 \(w\) 的树的路径中(不包含 \(sdom(w)\)), \(sdom(u)\) 最小的点,那么有
- \(idom(w) = sdom(u)\) \((sdom(u) = sdom(w))\)(4)
- \(idom(w) = idom(u)\) \((sdom(u) < sdom(w))\)(5)
\(proof\):首先我们考虑对于任意一对 \((u,x)\) 如何证明 \(idom(u) = x\),我们仅需证明两点
- \(x \in dom(u)\)
- \(\forall v \in (x -> u), v != x\) , 有\(v \notin dom(u)\)
(4) 的证明
若 \(sdom(u) = sdom(w)\),我们考虑 \(sdom(u)\) 必然是支配点,假设存在一个点 \(x\) 使得 \(r -> x \rightarrow o -> w\), 且 \(x < u\),那么 \(sdom(o) < sdom(u)\),矛盾
接着我们考虑假设有一个点 \(y\) 在 \(sdom(u) -> u\)中,且是支配点,显然和引理\((2)\)矛盾
(5) 的证明
先证\(idom(u) \in dom(w)\)
考虑反证,若删掉 \(idom(u)\) 后,仍然存在 \((r,w) \in path\),那么必然是 \(r -> x \rightarrow o -> w\),且 \(o > u,x < idom(u)\),且 \(sdom(o) = x < sdom(u)\),矛盾
再证不存在更近的支配点
依然考虑反证,假设存在这样的点,不妨设为 \(y\),我们考虑将其删去,继续分类讨论
- (1),\(y \notin (u,w)\) 那么因为 \(idom(u)\) 已经是最近的支配点,那么必然存在 \((r,u) \in path\),又因为存在 \(u -> w\),那么必然存在\((r,w) \in path\),矛盾
- (2),\(y \in (u,w)\),\(idom(w)\) 一定是 \(sdom(w)\)的祖先,矛盾
那么我们根据这个定理,只需求出 \(sdom\) ,就可以在 \(O(n\log n)\)的时间内,求出 \(idom\)
考虑 \(sdom\) 怎么求
定理 (sdom 的另一种简化定义)(6)
- \(S1(w) = \{(v,w) \in E,v < w\}\)
- \(S2(w) = \{sdom(u)|u > w,\exist(v,w),(u -> v) \in T \}\)
- \(sdom(w) = \min\{v|v \in S1(w) \cup S2(w)\}\)
用人话说,就是我们考虑所有\((v,w) \in E\),如果\(v < w\),那么\(v\)可以纳入侯选集合,否则,我们考虑其所有祖先\(u\)满足\(u > w\),\(sdom(u)\)可以纳入侯选集合
证明:\(S1\)的证明是显然的,我们考虑 \(S2\) 怎么证,其实也是显然的,我们考虑这么一条路径,\(sdom(u) -> u -> v -> w\),满足条件
有了以上这些定理和引理,我们考虑具体的构造过程
构造过程
大体可以分成两部分。
- 得到 \(sdom\) 数组
- 由 \(sdom\) 得到 \(idom\)
以下内容基本参考 cz_xuyixuan 的 blog,先膜拜
算法基本流程
- 初始化、跑一遍 \(DFS\) 得到 \(T\).
- 按标号从大到小求出 \(sdom\)
- 求出所有能确定的 \(idom\), 剩下的点记录下和哪个点的 \(idom\) 是相同的
- 按照标号从小到大再跑一次,得到所有点的 \(idom\)
第一步显然
第二步和第三步可以一起做,我们可以考虑维护一个带权并查集,每个点维护其到根 \(sdom\) 的最小值,按 \(dfs\) 序从大到小扫一遍,每当我们计算完 \(w\) ,我们可以寻找当前所有 \(sdom(u) = fa(w)\) 的点 \(u\) , 计算 \(\min\{sdom(v)| v \in (sdom(u)->u)\}\),这个可以用 \(\textstyle vector\) 然后清空掉,容易发现这样维护的只有当前一条链,然后我们可以通过定理 (4), (5) ,来得到 \(idom(u) = sdom(u)\) 或者 \(idom(u) = idom(v)\),不过我们现在可能还不知道 \(idom(v)\) , 所以先打个标记
最后,从小到大扫一遍,得到 \(idom\) 数组
代码如下:也就调了一百万年吧
#include<bits/stdc++.h>
using namespace std;
int read() {
char c = getchar();
int x = 0;
while(c < '0' || c > '9') c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48,c = getchar();
return x;
}
const int _ = 5e5 + 7;
int sdom[_],idom[_];
vector<int>E[_];
vector<int>O[_];
vector<int>T[_];
vector<int>G[_];
int par[_];
#define pb push_back
int n,m;int siz[_];
int dfn[_],dfncnt,isdfn[_];
int fa[_];int mn[_];
bool cmp(int u,int v) {
return dfn[u] < dfn[v];
}
int Min(int u,int v) {
if(cmp(u,v)) return u;
return v;
}
int get(int u) {
if (fa[u] == u) return u;
int tmp = fa[u];
fa[u] = get(fa[u]);
if(dfn[sdom[mn[u]]] > dfn[sdom[mn[tmp]]]) mn[u] = mn[tmp];
return fa[u];
}
void dfs(int u) {
dfn[u] = ++dfncnt;isdfn[dfncnt] = u;
sdom[u] = u;
for (auto v:E[u]) {
if(!dfn[v]){
// cout<<"E"<<' '<<u <<' '<<v<<'\n';
par[v] = u;
dfs(v);
}
}
}
void tree(int u) {
siz[u] = 1;
for (auto v:T[u]) {
// cout << u << ' ' <<v<<"hxsb"<<'\n';
tree(v);
siz[u] += siz[v];
}
}
int main() {
n = read(),m = read();
for (int i = 1; i <= m; ++i) {
int u = read(),v = read();
E[u].pb(v);O[v].pb(u);
}
E[0].pb(1);O[1].pb(0);
dfs(0);
for (int i = 1; i <= n; ++i) fa[i] = i;
for (int i = n + 1; i >= 1; --i) {
int u = isdfn[i];
for (auto v:O[u]) {
if (cmp(v,u)){
sdom[u] = Min(sdom[u],v);
}
else {
get(v);
sdom[u] = Min(sdom[u],sdom[mn[v]]);
}
}
G[sdom[u]].pb(u);
mn[u] = u;
for (auto v:E[u]) {
if(dfn[v] > dfn[u] && par[v] == u) {
fa[v] = u;
get(v);
}
}
for (auto v:G[par[u]]) {
// if(v == 4) cout<<<<'\n';
get(v);
int w = mn[v];
// if(v == 4) cout<<w<<'\n';
if(sdom[w] == sdom[v]) idom[v] = sdom[v];
else idom[v] = w;
}
get(u);
G[par[u]].clear();
}
for (int i = 1; i <= n + 1; ++i) {
int u = isdfn[i];
if (idom[u] == sdom[u]) continue;
else idom[u] = idom[idom[u]];
}
// for (int i = 1; i <= n; ++i) cout<<idom[i]<<' ';
// cout<<'\n';
for (int i = 1; i <= n; ++i) T[idom[i]].pb(i);
tree(0);
for (int i = 1; i <= n; ++i) printf("%d ",siz[i]);
cout<<'\n';
return 0;
}
一些习题:
[省选联考 2021 A 卷] 支配
感觉是模板题,不建议做