支配树是一种将有向图转化为一棵树的十分有效的方法。
在这棵树中,每个点的父亲就是一个离它最近的点使得去掉这个点之后,一号点和这个点就会不连通。
如果这张图是一个普通的\(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; }