支配树学习笔记
支配树是一种将有向图转化为一棵树的十分有效的方法。
在这棵树中,每个点的父亲就是一个离它最近的点使得去掉这个点之后,一号点和这个点就会不连通。
如果这张图是一个普通的\(DAG\),那么求解支配树的方法比较简单,直接按照拓扑序去做,对于一个点,它在支配树上的父亲就是所有能够到达它的点在树上的\(LCA\),直接求即可。
但是现在这张图变成了一张一般的有向图。
有向图上的支配树是用Lengauer-Tarjan 算法来完成的。
在这个算法中,引入了半支配点。
半支配点就是对于一个点,存在一条从半支配点到当前点的路径使得中间经过的所有点的dfs序都大于\(u\)点。
求法的话就是按照\(dfs\)序倒序枚举,对每个点我们用带权并查集求出所有\(dfs\)序比他大的点的父亲的\(min\)就是半支配点了。
可以发现半支配点也会构成一个树形的关系,那么我们求支配点的时候把半支配点到当前点的链拿出来,查找一下这条链上所有点的半支配的最浅点是否是半支配点,需要处理一些情况。
#include<bits/stdc++.h>
#define N 200009
using namespace std;
typedef long long ll;
vector<int>vec[3][N];
int dfn[N],rec[N],f[N],mn[N],sdom[N];
int idom[N],fa[N],cnt[N];
int n,m;
inline ll rd(){
ll x=0;char c=getchar();bool f=0;
while(!isdigit(c)){if(c=='-')f=1;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return f?-x:x;
}
void dfs(int u){
dfn[u]=++dfn[0];
rec[dfn[0]]=u;
for(auto v:vec[0][u])
if(!dfn[v]){
fa[v]=u;dfs(v);
}
}
int find(int u){
if (u==f[u]) return u;
int rt=find(f[u]);
if (dfn[sdom[mn[f[u]]]]<dfn[sdom[mn[u]]]) mn[u]=mn[f[u]];
return f[u]=rt;
}
inline void solve(){
n=rd();m=rd();
int u,v;
for(int i=1;i<=m;++i){
u=rd();v=rd();
vec[0][u].push_back(v);
vec[1][v].push_back(u);
}
dfs(1);
for(int i=1;i<=n;++i)mn[i]=f[i]=sdom[i]=i;
for(int i=dfn[0];i>=2;--i){
int u=rec[i];
for(auto v:vec[1][u]){
if(!dfn[v])continue;
find(v);
if(dfn[sdom[mn[v]]]<dfn[sdom[u]])sdom[u]=sdom[mn[v]];
}
f[u]=fa[u];
vec[2][sdom[u]].push_back(u);
u=fa[u];
for(auto v:vec[2][u]){
find(v);
idom[v]=u==sdom[mn[v]]?u:mn[v];
}
vec[2][u].clear();
}
for (int i=2;i<=dfn[0];++i){
int u=rec[i];
if (idom[u]^sdom[u]) idom[u]=idom[idom[u]];
}
for(int i=dfn[0];i>1;--i){
cnt[idom[rec[i]]]+=++cnt[rec[i]];
}
++cnt[1];
for(int i=1;i<=n;++i)printf("%d ",cnt[i]);
}
int main(){
solve();
return 0;
}