支配树学习笔记

支配树是一种将有向图转化为一棵树的十分有效的方法。

在这棵树中,每个点的父亲就是一个离它最近的点使得去掉这个点之后,一号点和这个点就会不连通。

如果这张图是一个普通的\(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;
}
posted @ 2021-08-28 00:58  comld  阅读(81)  评论(0编辑  收藏  举报