【学习小记】支配树【图论】

Preface

给定一个有向图和一个起点 s t st st,我们需要知道起点到某个点的关于必经点的信息。
若起点到点v的所有路径均经过点u,则我们称点u支配点v,显然一个点支配自己本身

顾名思义,支配树就是由某些支配关系构成的树。

定义

约定一些记号
( u , v ) (u,v) (u,v),表示一条从u到v的有向边
f a ( u ) fa(u) fa(u),表示 u u u在DFS树上的父亲。
d f n ( i ) dfn(i) dfn(i)为从 s t st st开始DFS整个图, i i i号点的 d f s dfs dfs时间戳。
P a t h ( u , v ) Path(u,v) Path(u,v),表示DFS树上 u u u v v v的路径上的点集
P a t h ( u , v ) / u Path(u,v)/u Path(u,v)/u表示路径上点集中除去点 u u u剩余的集合

我们定义一个点 u u u的最近支配点 i d o m ( u ) idom(u) idom(u),它支配 u u u,且它也被除u以外的所有支配u的点支配

性质


显然 i d o m ( u ) idom(u) idom(u)唯一存在,且若我们重新建一个图,所有的 i d o m ( u ) idom(u) idom(u) u u u连一条边,那么这个新图是一棵以 s t st st为根的树,也就是说 i d o m idom idom不会成环(可以用反证法简单证明)。
这棵树就是我们说的支配树。


i d o m ( u ) idom(u) idom(u)一定是 u u u d f s dfs dfs树上的祖先。(一样可以反证法简单证明)


问题就是要求出 i d o m idom idom

求解

为了求出 i d o m idom idom,我们引入半必经点的概念。

半必经点

回归tarjan求强连通分量的算法,我们来观察DFS过程中会出现哪些边。

  • 树边,DFS树上的父亲——儿子边。
  • 后向边,祖先向后代连的非树边的边。
  • 返祖边,DFS序大的后代向DFS序小的祖先连的边
  • 横叉边,DFS序大的点向DFS序小的点连的边,且没有祖先后代关系。

容易证明不存在上面四种边以外的边。

s e m i ( u ) semi(u) semi(u)表示从 u u u开始,沿着边反着走,能够中途不经过u的祖先,最后到达的深度最浅( d f n dfn dfn最小)的 u u u的祖先。

形式化的, s e m i ( u ) semi(u) semi(u) u u u的祖先,且存在原图中的一条路径 p 1 . . . p k p_1...p_k p1...pk p 1 = s e m i ( u ) , p k = u , ∀ i = 2 k − 1 d f n ( p i ) > d f n ( u ) p_1=semi(u),p_k=u,\forall_{i=2}^{k-1}dfn(p_i)>dfn(u) p1=semi(u),pk=ui=2k1dfn(pi)>dfn(u)

可以发现这两种定义是等价的,因为不可能通过反向边走到DFS序更小的且不是祖先的点去。

我们考虑它可能怎么走,要么通过树边/后向边的反向边直接走到一个祖先,要么通过返祖边/横叉边的反向边走到某些 d f n dfn dfn更大的点 p r e u pre_u preu,然后在 P a t h ( s t , p r e u ) Path(st,pre_u) Path(st,preu)上找一个 w w w,满足 d f n ( w ) ≥ d f n ( u ) dfn(w)\geq dfn(u) dfn(w)dfn(u),所有的 s e m i ( w ) semi(w) semi(w)的最浅的那个就用来更新 s e m i ( u ) semi(u) semi(u)

可以发现最浅的 s e m i ( w ) semi(w) semi(w)一定是 u u u的祖先,因此我们不必担心不合法的问题。

那么我们就得到了 s e m i semi semi的求法,按照DFS序倒序枚举所有点 u u u,按照上面的做法来不断更新,查询祖先可以用带权并查集来做,求出一个点的 s e m i semi semi之后将它的所有树边儿子与它合并即可。

问题在于求 i d o m idom idom
我们考虑 v ∈ P a t h ( s e m i ( u ) , u ) / s e m i ( u ) v\in Path(semi(u),u)/semi(u) vPath(semi(u),u)/semi(u),找到 s e m i ( v ) semi(v) semi(v)最浅的一个。

有定理:若 s e m i ( v ) = s e m i ( u ) semi(v)=semi(u) semi(v)=semi(u),则 i d o m ( u ) = s e m i ( u ) idom(u)=semi(u) idom(u)=semi(u),否则 i d o m ( u ) = i d o m ( v ) idom(u)=idom(v) idom(u)=idom(v)
这个定理请自行理解…我也不会证

有了这个定理之后我们可以将 u u u挂在 s e m i ( u ) semi(u) semi(u)上,扫到 s e m i ( u ) semi(u) semi(u)的时候就找到 u u u相应的 v v v,若 s e m i ( u ) = s e m i ( v ) semi(u)=semi(v) semi(u)=semi(v)就直接 i d o m ( u ) = s e m i ( u ) idom(u)=semi(u) idom(u)=semi(u),否则因为此时 i d o m ( v ) idom(v) idom(v)还没有求出来,我们可以先将 i d o m ( u ) idom(u) idom(u)指向 v v v,最后全部都做完以后按照DFS序正序扫一遍将这部分的 i d o m ( u ) idom(u) idom(u)改成 i d o m ( i d o m ( u ) ) idom(idom(u)) idom(idom(u))即可。

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)(因为不能按秩合并)

Code

洛谷P5180,支配树模板题。

#include <bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define N 200005
#define M 300005
using namespace std;
vector<int> pre[N];
int fs[N],nt[M],dt[M],pr[M],n,m,m1;

int semi[N],idom[N],ft[N],fp[N],dfn[N],dfw[N],fa[N];
vector<int> ids[N];
int gmin(int x,int y) 
{
	return (!y||(dfn[x]<dfn[y]&&x))?x:y;
}
void link(int x,int y)
{
	nt[++m1]=fs[x];
	dt[fs[x]=m1]=y;
}
void dfs(int k)
{
	dfw[dfn[k]=++dfn[0]]=k;
	for(int i=fs[k];i;i=nt[i])
	{
		int p=dt[i];
		if(!dfn[p]) fa[p]=k,dfs(p);
		if(dfn[k]<dfn[p]) semi[p]=gmin(semi[p],k);
		else pre[p].push_back(k);
	}
}
int getf(int k)
{
	if(!ft[k]||ft[k]==k) return k;
	int p=getf(ft[k]);
	fp[k]=(dfn[semi[fp[k]]]<dfn[semi[fp[ft[k]]]])?fp[k]:fp[ft[k]];
	return ft[k]=p;
}
int fd(int k)
{
	getf(k);return fp[k];
}
void merge(int x,int y)
{
	int fx=getf(x),fy=getf(y);
	ft[fy]=fx,fp[fy]=(dfn[semi[fp[fy]]]<dfn[semi[fp[fx]]])?fp[fy]:fp[fx];
}
void make()
{
	fod(i,n,1)
	{
		int k=dfw[i];
		if(i>1)
		{
			for(int u:pre[k])
			{	
				int v=fd(u);
				semi[k]=gmin(semi[k],semi[v]);
			}
			ids[semi[k]].push_back(k);	
		}
		for(int u:ids[k])
		{
			int v=fd(u);
			idom[u]=(semi[v]==k)?k:v; 
		}
		fp[k]=k;
		for(int i=fs[k];i;i=nt[i]) if(fa[dt[i]]==k) merge(k,dt[i]);
	}
	for(int i=2,k=dfw[2];i<=n;++i,k=dfw[i]) if(idom[k]!=semi[k]) idom[k]=idom[idom[k]];
}
int sz[N];
int main()
{
	cin>>n>>m;
	fo(i,1,m)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		link(x,y),pre[y].push_back(x);
	}
	dfs(1);
	make();
	fod(i,n,1)
	{
		sz[dfw[i]]++;
		sz[idom[dfw[i]]]+=sz[dfw[i]];	
	}
	fo(i,1,n) printf("%d ",sz[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值