摘抄自 oi-wiki 。
考虑在任意 有向图 上钦定一个入口节点 s s s,对于一个节点 u u u,若从 s s s到 u u u的每一条路径都经过某一个节点 v v v,那么称 v v v支配 u u u,记作 v dom u v\operatorname{dom}u vdomu。
对于从 s s s出发无法到达的结点,讨论其支配关系是没有意义的,因此假设 s s s能到达图上任何一个节点。
引理1: s s s是所有节点的支配点;任意一个节点都是其自身的支配点
引理2:仅考虑简单路径得出的支配关系与所有路径得出的关系相同
引理3:如果 u dom v u\operatorname{dom}v udomv, v dom w v\operatorname{dom}w vdomw,则 u dom w u\operatorname{dom}w udomw
引理4:如果 u dom v , v dom u u\operatorname{dom}v,v\operatorname{dom}u udomv,vdomu,则 u = v u=v u=v
引理5:若 u ≠ v ≠ w u\ne v\ne w u=v=w, u dom w u\operatorname{dom}w udomw且 v dom w v\operatorname{dom}w vdomw,则有 u dom v u\operatorname{dom}v udomv或 v dom u v\operatorname{dom}u vdomu
证明:考虑一条 s → u → v → w s\to u\to v\to w s→u→v→w的路径,若 u u u不支配 v v v,则存在一条 s → v → w s\to v\to w s→v→w的路径,与 u dom w u\operatorname{dom}w udomw矛盾(注意我们只考虑简单路径的情况)
将任意节点 u u u的支配点中,除自身外与自己距离最近的节点 v v v称作 u u u的直接支配点,记作 idom ( u ) = v \operatorname{idom}(u)=v idom(u)=v。
比较直观的理解:设 u u u的支配点集为 { s 1 , s 2 , . . . , s k } \{s_1,s_2,...,s_k\} {s1,s2,...,sk},则一定存在一条路径 s → s 1 → s 2 → . . . → s k → u s\to s_1\to s_2\to ...\to s_k\to u s→s1→s2→...→sk→u,显然 s k s_k sk就是 u u u的直接支配点。
将除 s s s外每一个节点 u u u从 idom ( u ) \operatorname{idom}(u) idom(u)向 u u u连边,我们就得到了一颗支配树。
结论:对于一个节点 u u u的支配点集 S S S,若 v ∈ S v\in S v∈S满足 ∀ w ∈ S \ { u , v } , w dom v \forall w\in S\ \backslash \ \{u,v\},w\operatorname{dom}v ∀w∈S \ {u,v},wdomv,则 idom ( u ) = v \operatorname{idom}(u)=v idom(u)=v(结合前面的理解不难证明,这个可能构造题会用到)
对于 D A G DAG DAG,我们可以直接根据拓扑序求解。
引理6:在有向图上, v dom u v\operatorname{dom}u vdomu当且仅当 ∀ w ∈ p r e ( u ) , v dom w \forall w\in pre(u),v\operatorname{dom} w ∀w∈pre(u),vdomw
因此, u u u的支配点一定是其所有前驱节点在支配树上的公共祖先。使用 L C A LCA LCA算法即可解决。
求解任意有向图中支配树的方法:
约定这里 w → v w\to v w→v指的是存在 DFS 树上从 w w w到 v v v的路径。注意这个表述并不严谨,更严谨的记号可以参考 这篇博客。后文一些引理的证明也 参考了这篇博客 。
首先从 s s s出发对有向图进行 DFS ,所经过的点和边形成了一棵树 T T T。令 d f n ( u ) dfn(u) dfn(u)表示节点 u u u第几个被遍历到;定义 u < v u
引理7(路径引理):如果两个点 v , w v,w v,w满足 v ≤ w v\le w v≤w,那么任意 v v v到 w w w的路径经过 v , w v,w v,w的公共祖先(不一定是 L C A LCA LCA)
证明主要是利用横叉边一定是从大的点到小的点。
定义节点 u u u的半支配点为,从这个节点 v v v出发存在一条路径,路径上除了 u , v u,v u,v之外的每个节点都大于 u u u的结点中最小的那一个,记作 sdom ( u ) \operatorname{sdom}(u) sdom(u)。
引理8:对于任意节点 u u u, sdom(u) < u \text{sdom(u)}sdom(u)<u。
显然 f a ( u ) < u fa(u)fa(u)<u,并且是合法的半支配点。
引理9:对于任意节点 u u u, idom(u) \text{idom(u)} idom(u)是其在 T T T上的祖先
引理10:对于任意节点 u u u, sdom ( u ) \text{sdom}(u) sdom(u)是其在 T T T上的祖先
如果 v v v不是 u u u的祖先,并且 v < u vv<u,那么 v v v到 u u u的路径一定经过 u , v u,v u,v的公共祖先(路径引理),不满足 v v v是支配点的条件。
引理11:对于任意节点 u u u, idom ( u ) \text{idom}(u) idom(u)是 sdom ( u ) \text{sdom}(u) sdom(u)的祖先
引理12:对于任意节点 u ≠ v u\ne v u=v满足 v v v是 u u u的祖先,则要么有 v v v是 idom(u) \text{idom(u)} idom(u)的祖先,要么 idom(u) \text{idom(u)} idom(u)是 idom ( v ) \text{idom}(v) idom(v)的祖先
证明:对于任意在 v v v和 idom ( v ) \text{idom}(v) idom(v)之间的节点 w w w,一定存在一条不经过 w w w的,从 s s s到 idom ( v ) \text{idom}(v) idom(v)再到 v v v的路径。因此这些节点一定不是 idom(u) \text{idom(u)} idom(u),因此 idom(u) \text{idom(u)} idom(u)要么是 v v v的后代,要么是 idom(v) \text{idom(v)} idom(v)的祖先
引理13: sdom ( u ) = min ( v ∣ ( v , u ) ∈ E , v < w ∪ sdom ( w ) ∣ w > u , ∃ ( v , u ) ∈ E , w → v ) \text{sdom}(u)=\min(v|(v,u)\in E,v
可以从大到小枚举点,用带权并查集维护,从而求得所有点的半支配点。
引理14:对于任意 u ≠ r u\ne r u=r,如果所有 sdom(u) \text{sdom(u)} sdom(u)和 u u u之间(不包括 sdom(u) \text{sdom(u)} sdom(u))的点 v v v满足 sdom(v) ≥ sdom(u) \text{sdom(v)}\ge \text{sdom(u)} sdom(v)≥sdom(u),那么 idom(u) = sdom(u) \text{idom(u)}=\text{sdom(u)} idom(u)=sdom(u)
证明:只需证明 sdom(u) \text{sdom(u)} sdom(u)支配 u u u即可。考虑任意 s s s到 w w w的路径,取上面最后一个编号小于 sdom(u) \text{sdom(u)} sdom(u)的 x x x,则一定存在 y y y满足 y y y在 sdom(u) \text{sdom(u)} sdom(u)和 u u u之间。同理,考虑取最小的的 y y y,如果 y y y不是 sdom(u) \text{sdom(u)} sdom(u),那么根据条件, sdom(y) ≥ sdom(u) \text{sdom(y)}\ge \text{sdom(u)} sdom(y)≥sdom(u),所以 x x x不可能是 sdom(y) \text{sdom(y)} sdom(y),因此从 x x x到 y y y的路径上也存在一个 sdom(y) \text{sdom(y)} sdom(y)和 y y y之间的节点 w w w(这与之前的推理是一样的),这与 y y y最小矛盾。
这个结论比较重要,能让我们比较直观的感受到半支配点的作用。
引理15:对于任意 u u u,令 v v v为 sdom(u) \text{sdom(u)} sdom(u)和 u u u之间(不包括 sdom(u) \text{sdom(u)} sdom(u)) sdom(v) \text{sdom(v)} sdom(v)最小的一个,并且 sdom(v) ≤ sdom(u) \text{sdom(v)}\le \text{sdom(u)} sdom(v)≤sdom(u),那么 idom(u) = idom(v) \text{idom(u)}=\text{idom(v)} idom(u)=idom(v)
证明:条件可以写成 sdom(v) → sdom(u) → v → u \text{sdom(v)}\to \text{sdom(u)}\to v\to u sdom(v)→sdom(u)→v→u。
由引理12可知 idom(u) → idom(v) \text{idom(u)}\to \text{idom(v)} idom(u)→idom(v) 或 v → idom(u) v\to \text{idom(u)} v→idom(u),排除后面那种(引理11),只需证明 idom(v) \text{idom(v)} idom(v)支配 u u u。这是比较显然的。
引理16:对于任意 u u u,令 v v v为 sdom ( u ) \text{sdom}(u) sdom(u)和 u u u之间(不包括 sdom(u) \text{sdom(u)} sdom(u)) sdom ( v ) \text{sdom}(v) sdom(v)最小的一个,则:
idom(u) = { sdom(u) sdom(v) = sdom(u) idom(v) sdom(v) < sdom(u) \text{idom(u)}= \begin{cases} \text{sdom(u)}& \text{sdom(v)}=\text{sdom(u)} \\ \text{idom(v)}&\text{sdom(v)}<\text{sdom(u)} \end{cases} idom(u)={sdom(u)idom(v)sdom(v)=sdom(u)sdom(v)<sdom(u)
说白了就是前两个引理。
可以发现求 v v v的过程和求半支配点是类似的,因此当从大到小处理到 sdom(u) \text{sdom(u)} sdom(u)的时候就可以顺便求出 u u u对应的信息。最后再从小到大遍历一遍即可。
复杂度 O ( n + m ) O(n+m) O(n+m)。非常高效。和SAM一样
吐槽:oi-wiki上的写法是错的,会被叉。网上的代码就没几个靠谱的。。。
#include
#define ll long long
#define fi first
#define se second
#define pb push_back
#define inf 0x3f3f3f3f
using namespace std;
const int N=2e5+5;
int n,m,mn[N],dfn[N],num,ps[N],idm[N],sdm[N],fa[N],fth[N],sz[N];
vector<int>G[N],G2[N],G3[N],G4[N];
void dfs(int u){
dfn[u]=++num,ps[num]=u;
for(auto v:G[u]){
if(!dfn[v])fth[v]=u,dfs(v);
}
}
int find(int x){
if(fa[x]==x)return x;
int tmp=fa[x];
fa[x]=find(fa[x]);
if(dfn[sdm[mn[tmp]]]<dfn[sdm[mn[x]]]){
mn[x]=mn[tmp];
}return fa[x];
}
void solve(){
dfs(1);
for(int i=1;i<=n;i++)fa[i]=sdm[i]=mn[i]=i,idm[i]=1;
for(int i=num;i>=2;i--){
int u=ps[i],res=inf;
for(auto v:G2[u]){
if(dfn[v]<dfn[u]){
res=min(res,dfn[v]);
}
else{
find(v);
res=min(res,dfn[sdm[mn[v]]]);
}
}
//u=fth[u];
for(auto v:G3[u]){
find(v);
if(sdm[mn[v]]==sdm[v]){
idm[v]=u;
}
else{
idm[v]=mn[v];
}
}
sdm[u]=ps[res];
G3[sdm[u]].pb(u);
fa[u]=fth[u];
}
for(int i=2;i<=num;i++){
int u=ps[i];
if(idm[u]!=sdm[u]){
idm[u]=idm[idm[u]];
}
}
}
void build(){
for(int i=2;i<=n;i++)G4[idm[i]].pb(i);
}
void dfs2(int u){
sz[u]=1;
for(auto v:G4[u])dfs2(v),sz[u]+=sz[v];
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++){
int x,y;cin>>x>>y;
G[x].pb(y),G2[y].pb(x);
}
solve();
build();
dfs2(1);
for(int i=1;i<=n;i++)cout<<sz[i]<<" ";
}