支配树学习笔记
前言
本文参考了:
- 2020 年信息学奥林匹克中国国家集训队论文:陈孙立,浅谈支配树及其应用
本文和上文在构建支配树的切入点上会有些许不同,尝试用更加容易理解的方式解析支配树的构建。
支配树的存在性及其定义
本文考察的是一个 \(n\) 个点 \(m\) 条边的有向图 \(G=(V,E)\),设其的源为 \(s\),满足 \(s\) 能到达图上的所有点。
定义 \(x\) 支配 \(y\) 当且仅当所有从 \(s\) 到 \(y\) 的路径都会经过 \(x\),称 \(x\) 为 \(y\) 的一个支配点。
显然我们在考察 \(x\) 是否支配 \(y\) 时只需要考虑所有从 \(s\) 到 \(y\) 的简单路径是否都经过 \(x\) 即可,所以我们下文中的 “路径” 在没有特殊说明的情况下指的都是简单路径。
-
性质 1:若不同的三个点 \(x,y,z\) 满足 \(x,y\) 均支配 \(z\),那么必然满足 \(x\) 支配 \(y\) 或 \(y\) 支配 \(x\)。
证明:不妨设某一条从 \(s\) 到 \(z\) 的路径为 \(s,\dots,x,\dots,y,\dots,z\),若 \(x\) 不支配 \(y\),那么就存在一条不经过 \(x\) 的从 \(s\) 到 \(z\) 的路径,与 \(x\) 支配 \(z\) 矛盾。
根据这条性质,可知对于任意一点 \(x\neq s\) 以及它的支配点集合 \(S=\{y|y\text{ 支配 }x\}\),\(S\) 中的点在支配关系上构成全序集,那么我们找到 \(S\) 中除 \(x\) 以外 “最小” 的元素 \(z\),即满足 \(S\) 中除 \(x\) 外的所有点都支配 \(z\),我们称 \(z\) 为 \(x\) 的直接支配点,记为 \(z=idom(x)\)。
再根据支配性的传递性容易证明,\(x\) 的支配点集合等于 \(idom(x)\) 的支配点集合并上 \(\{x\}\)。
那么如果我们对于每一个 \(x\neq s\) 都连一条 \(idom(x)\to x\) 的边,容易证明这会构成一棵以 \(s\) 为根的外向树,称为 \((G,s)\) 的支配树。根据上面的推导可知 \(x\) 支配 \(y\) 当且仅当支配树上 \(x\) 是 \(y\) 的祖先。
支配树的构建
对于 DAG 的支配树:我们可以直接拓扑排序,一个点的直接支配点即为它在 DAG 上的所有入点在支配树上的 LCA,倍增维护即可,时间复杂度 \(O((n+m)\log n)\)。
下面是对于更一般的图的情况:
考虑从 \(s\) 出发的任意一棵 DFS 生成树,每个点会有一个 DFS 序。为了方便,对于两个点 \(x,y\),我们定义 \(x<y\) 表示 \(x\) 的 DFS 序小于 \(y\) 的 DFS 序,类似地定义 \(x>y\)、点集中的最大点和最小点等概念。
我们用 \(path[x,y]\) 表示树上从 \(x\) 到 \(y\) 的简单路径上的点集,注意区间可开可闭(意为端点是否包含)。
显然一个点的支配点只可能是它在 DFS 树上的祖先。于是对于一个点 \(x\),对于它在 DFS 树上的某个严格祖先 \(y\),我们先考虑判定 \(y\) 是否为 \(x\) 的支配点。
设 \(sdom(x)\) 表示 \(x\) 的所有满足右述条件的祖先 \(f\) 中深度最小(或 DFS 序最小)的那个:存在一条路径 \(v_0=f,v_1,\dots,v_k=x\) 满足对于任意的 \(i\in [1,k-1]\) 都有 \(v_i\not\in path[f,x]\)。
即 \(sdom(x)=\min\{f|f\text{ 为 }x\text{ 的祖先,且存在一条路径 }v_0=f,v_1,\dots,v_k=x\text{ 满足对于任意的 }i\in[1,k-1]\text{ 都有 }v_i\not\in path[f,x]\}\)。
-
定理 1:对于 \(x\neq s\),\(x\) 的某个严格祖先 \(y\) 是 \(x\) 的支配点等价于 \(\forall v\in path(y,x],sdom(v)\geq y\)。
证明:首先若后者不成立,则前者显然也不成立。然后考虑证明前者不成立必然导致后者不成立。
前者不成立等价于存在一条从 \(s\) 到 \(x\) 的路径 \(P\) 不经过 \(y\)。设 \(v\) 为 \(P\) 中第一次出现在 \(path(y,x]\) 上的点,\(w\) 为 \(P\) 中 \(v\) 之前最后一次出现在 \(path[s,y)\) 上的点(显然 \(v,w\) 必然存在,因为 \(x,s\) 分别为它们的候选),那么 \(w\) 就是 \(sdom(v)\) 的一个候选点,因为 \(P\) 中 \(w,\dots,v\) 的部分就满足 \(sdom(v)\) 的要求。而且 \(w<y\),于是必然导致 \(y\leq sdom(v)\) 不成立。
推论:\(idom(x)\leq sdom(x)\)。
事实上根据定理1,假设我们已经知道了 \(sdom\),我们就已经可以 \(O(n\log n)\) 求出所有点的 \(idom\) 了,具体实现是线段树实现区间覆盖以及线段树上二分。
但我们需要更加优美的做法。
事实上,如果我们深入发掘,还会有一些更强的结论。
-
引理 1:若 \(v\) 是 dfs 树上 \(w\) 的一个祖先,则 \(v\) 是 \(idom(w)\) 的祖先或 \(idom(w)\) 是 \(idom(v)\) 的祖先。
即 \(idom(w)\) 不可能存在于 \(path(idom(v),v)\) 中。
证明:若 \(idom(w)\) 存在于 \(path(idom(v),v)\) 中,那么根据定义,所有形如 \(s,\dots,v,\dots,w\) 的路径都要经过 \(idom(w)\),即所有形如 \(s,\dots,v\) 的路径都要经过 \(idom(w)\)(否则可以直接再从 \(v\) 沿 dfs 树走到 \(w\) 而不经过 \(idom(w)\)),那么 \(idom(w)\) 也是 \(v\) 的一个支配点,而由于可以沿 dfs 树从 \(idom(v)\) 走到 \(idom(w)\),所以 \(idom(v)\) 不符合直接支配点定义,矛盾。
-
定理 2:对于 \(w\neq s\),若 \(\forall v\in path(sdom(w),w],sdom(v)\geq sdom(w)\),则 \(idom(w)=sdom(w)\)。
证明:结合定理1及其推论容易证明。
-
定理 3:对于 \(w\neq s\),设 \(u\) 是 \(path(sdom(w),w]\) 中 \(sdom\) 最小的点,则 \(idom(w)=idom(u)\)。
证明:以下证明建议自己画示意图表示各种点之间的大小关系。
根据假设可知,\(\forall v\in path(sdom(w),w],sdom(v)\geq sdom(u)\geq idom(u)\)。
根据 \(idom(u)\) 的定义和定理1可知,\(\forall v\in path(idom(u),u],sdom(v)\geq idom(u)\)。
根据 \(sdom(w)<u\) 可将二者结合起来,得到 \(\forall v\in (idom(u),w],sdom(v)\geq idom(u)\),根据定理1,可知 \(idom(u)\) 是 \(w\) 的支配点之一。
而根据定理1的推论可知 \(idom(w)\leq sdom(w)<u\),再结合引理1可知 \(idom(w)\leq idom(u)\),于是 \(idom(w)=idom(u)\)。
根据定理2和定理3,发现若 \(sdom\) 求出,那我们就已经可以求出 \(idom\) 了:因为 \(idom(w)\) 要么等于自己的 \(sdom(w)\),要么等于某个祖先 \(u\) 的 \(idom(u)\),于是按深度(DFS 序)扫一遍即可(详细过程后面会讲)。
于是问题转化为求 \(sdom\)。
如果这是一个 DAG,可以直接使用倍增,可惜它并不是。
接下来就是比较神奇的地方,我们将通过等价关系转化 \(sdom\) 的定义。
-
引理 2:对于非树边中的任意一条横叉边 \(y\to x\),必有 \(y>x\)。证明显然。
推论 1:若 \(x\leq y\),则任意 \(x\) 到 \(y\) 的路径都必须经过 \(x\) 和 \(y\) 在 dfs 树上的某一个公共祖先。
证明:若 \(x\) 为 \(y\) 的祖先则显然。否则:把 \(x,y\) 的所有公共祖先都从图中刨去,那么 dfs 树上只剩下若干棵子树且 \(x,y\) 不在同一棵子树。同一棵子树内对应着一段连续的 dfn 区间,通过树边/返祖/后向边我们只能在同一棵子树内移动,而从一棵子树跳到另一棵子树只能通过横叉边,而且只能从 dfn 区间大的子树跳到 dfn 区间小的子树,于是不可能从 \(x\) 跳到 \(y\)。
-
定理 4:\(sdom\) 第一种定义:\(sdom(x)=\min\{f|f\text{ 为 }x\text{ 的祖先,且存在一条路径 }v_0=f,v_1,\dots,v_k=x\text{ 满足对于任意的 }i\in[1,k-1]\text{ 都有 }v_i\not\in path[f,x]\}\)。(原来的定义)
\(sdom\) 第二种定义:\(sdom(x)=\min\{y|\text{ 存在一条路径 }v_0=y,v_1,\dots,v_k=x\text{ 满足对于任意的 }i\in[1,k-1]\text{ 都有 }v_i>x\}\)。
这两种定义是等价的。
证明:证明分两步走,第一步证明第二种定义下的 \(sdom(x)\) 肯定满足第一种定义下的条件,第二步证明第一种定义下的 \(sdom(x)\) 肯定满足第二种定义下的条件。
证明第一步:令 \(y\) 为第二种定义下的 \(sdom(x)\)。只需证明 \(y\) 只可能是 \(x\) 的某个祖先即可,此时因为第二个定义中对路径的条件比第一个定义中的更严格,所以 \(y\) 肯定满足第一个定义的条件。
首先由于 \(x\) 在 dfs 树上的父亲已经满足第二种定义下 \(sdom(x)\) 的要求,所以 \(y<x\)。
那么若 \(y<x\) 且 \(y\) 不是 \(x\) 的祖先,根据引理2的推论可知任意 \(y\) 到 \(x\) 的路径都会经过 \(x\) 和 \(y\) 的某一个共同祖先,DFS 序小于 \(x\),不符合第二种定义下的要求。
所以 \(y\) 只可能是 \(x\) 的某个祖先。
证明第二步:令 \(f\) 为第一种定义下的 \(sdom(x)\),设令其满足第一种定义条件的路径为 \(v_0=f,v_1,\dots,v_k=x\),我们只需证明对于任意的 \(i\in [1,k-1]\) 均有 \(v_i>x\) 即可。
首先 \(v_1\sim v_{k-1}\) 中不可能出现 \(f\) 的祖先,否则我们找到 \(v_1\sim v_{k-1}\) 中最后一个出现的 \(f\) 的祖先,它肯定也满足第一种定义要求,而且它 DFS 序比 \(f\) 小,矛盾。
考虑若出现了 \(v_i<x\),那么此时 \(v_i\) 不为 \(x\) 的祖先(\(v_i\) 不为 \(f\) 的祖先且不会出现在 \(path[f,x]\) 中),\(x\) 不为 \(v_i\) 的祖先(否则 \(v_i>x\)),由引理2的推论可知 \(v_{i},\dots,v_{k}\) 中肯定会出现 \(v_i,x\) 的某个公共祖先(且它不等于 \(v_i\) 或 \(x\)),矛盾。
新的 \(sdom\) 的定义实际上把限制放松了,这使得我们更容易求 \(sdom\)。
-
定理 5:\(sdom(x)\) 由 \(x\) 的所有入边 \((y,x)\) 按如下方式得到的所有候选取最小值得到。
- 候选为 \(y\)。
- 若 \(y>x\),对于任意 \(z\) 为 \(y\) 在 DFS 树上满足 \(z>x\) 的祖先,\(sdom(z)\) 也都是候选。
证明:首先候选为 \(y\) 是枚举的 \(k=1\) 的情况。否则若 \(k>1\):
我们只需要证明 \(sdom(z)\) 一定是一个合法的候选,以及所有合法的候选一定都会被统计到。
首先 \(sdom(z)\) 一定是一个合法的候选很容易证明:只需要先沿着满足 \(sdom(z)\) 要求的路径从 \(sdom(z)\) 走到 \(z\),路径上除 \(sdom(z)\) 和 \(z\) 以外的点都 \(>z>x\),然后再沿着树边走到 \(y\) 再走到 \(x\) 即为一条满足 \(sdom(x)\) 要求的路径。
考虑一个合法的候选 \(f\) 以及令其满足 \(sdom(x)\) 条件的路径 \(v_0=f,v_1,\dots,v_k=x\)(\(k>1\)),那么 \(y=v_{k-1}\) 肯定会被枚举到,考虑找出 \(v_1,\dots,v_{k-1}\) 中第一次为 \(y\) 的祖先的点 \(z=v_{k'}\),我们可以证明 \(v_1,\dots,v_{k'-1}\) 均 \(>z\):若存在 \(v_i<z\),由引理2的推论可知 \(v_i,\dots,v_{k'}\) 中必须出现 \(v_{k'}\) 的严格祖先,与 \(z\) 的定义矛盾。于是这条满足 \(sdom(x)\) 要求的路径一定会被 \(sdom(z)\) 统计到。
那么我们就可以按 DFS 序从大到小枚举点 \(x\) 并求解 \(sdom(x)\):枚举 \(x\) 的入边 \((y,x)\),先用 \(y\) 更新 \(sdom(x)\),然后若 \(y>x\),找到 \(y\) 往上最高的祖先 \(z\) 仍满足 \(z>x\),那么 \(\forall z'\in path[z,y],z'>x\),于是用 \(path[z,y]\) 上所有点的 \(sdom\)(此时这些点的 \(sdom\) 都已经求好了)更新 \(sdom(x)\)。
过程可以用带权并查集维护,时间复杂度 \(O((n+m)\alpha(n))\)。
而对于求 \(idom\),让我们先回顾一下描述 \(sdom\) 和 \(idom\) 关系的两个定理:
- 定理 2:对于 \(w\neq s\),若 \(\forall v\in path(sdom(w),w],sdom(v)\geq sdom(w)\),则 \(idom(w)=sdom(w)\)。
- 定理 3:对于 \(w\neq s\),设 \(u\) 是 \(path(sdom(w),w]\) 中 \(sdom\) 最小的点,则 \(idom(w)=idom(u)\)。
显然我们需要先求出 \(path(sdom(w),w]\) 中 \(sdom\) 最小的点 \(u\)(记为 \(mins(w)\)),事实上这可以在求 \(sdom\) 过程中一起处理出来:对于某个点 \(w\) 求出 \(sdom(w)\) 后,把 \(w\) 当做一组询问记录在 \(sdom(w)\) 上,然后在扫到 \(sdom(w)\) 时利用带权并查集求出询问的答案即可(此时 \(w\) 在并查集上的祖先恰好就是 \(path(sdom(w),w]\) 上的所有单)。
最后再按深度(DFS 序)从小往大扫每一个点 \(w\),根据定理2、定理3:若 \(mins(w)=w\),则 \(idom(w)=sdom(w)\);否则 \(idom(w)=idom(mins(w))\)。
总时间复杂度 \(O((n+m)\alpha(n))\),空间复杂度 \(O(n+m)\)。
P5180【模板】支配树 参考代码:
#include<bits/stdc++.h>
#define N 200010
#define M 300010
#define INF 0x7fffffff
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^'0');
ch=getchar();
}
return x*f;
}
int n,m;
int idx,dfn[N],rk[N];
int idom[N],sdom[N],mins[N];
vector<int>in[N],te[N],que[N];
int dmin(int x,int y)
{
return dfn[x]<dfn[y]?x:y;
}
int smin(int x,int y)
{
return dfn[sdom[x]]<dfn[sdom[y]]?x:y;
}
namespace DFS
{
int cnt,head[N],nxt[M],to[M];
void adde(int u,int v)
{
to[++cnt]=v;
nxt[cnt]=head[u];
head[u]=cnt;
}
bool vis[N];
void dfs(int u)
{
vis[u]=1;
rk[dfn[u]=++idx]=u;
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(vis[v]) continue;
te[u].push_back(v);
dfs(v);
}
}
}
namespace fset
{
int fa[N],minn[N];
void init()
{
for(int i=1;i<=n;i++) fa[i]=minn[i]=i;
}
void find(int x)
{
if(x==fa[x]) return;
find(fa[x]);
minn[x]=smin(minn[x],minn[fa[x]]);
fa[x]=fa[fa[x]];
}
}
int main()
{
n=read(),m=read();
for(int i=1;i<=m;i++)
{
int u=read(),v=read();
DFS::adde(u,v);
in[v].push_back(u);
}
DFS::dfs(1);
fset::init();
dfn[0]=INF;
for(int i=n;i>=2;i--)
{
int x=rk[i];
for(int y:in[x])
{
sdom[x]=dmin(sdom[x],y);
if(dfn[y]>dfn[x])
{
fset::find(y);
sdom[x]=dmin(sdom[x],sdom[fset::minn[y]]);
}
}
que[sdom[x]].push_back(x);
for(int w:que[x])
{
fset::find(w);
mins[w]=fset::minn[w];
}
for(int v:te[x]) fset::fa[v]=x;
}
for(int w:que[rk[1]])
{
fset::find(w);
mins[w]=fset::minn[w];
}
for(int i=2;i<=n;i++)
{
int w=rk[i];
if(mins[w]==w) idom[w]=sdom[w];
else idom[w]=idom[mins[w]];
}
static int size[N];
for(int i=1;i<=n;i++) size[i]=1;
for(int i=n;i>=2;i--)
{
int u=rk[i];
size[idom[u]]+=size[u];
}
for(int i=1;i<=n;i++)
printf("%d ",size[i]);
return 0;
}