支配树 学习笔记

概念

对于一张有向图,1号点出发到一个点u,有一些点是会被所有路径经过的,定义这些点支配了u。不难发现支配关系形成一棵树,树上每个点的所有祖先都支配这个点,一个点在支配树上的父亲称为它的支配点。

实现

首先从 1 1 1开始 d f s dfs dfs,建立一颗 d f s dfs dfs树,求出每个点的 d f n dfn dfn。定义一个点 u u u半支配点 s d [ u ] sd[u] sd[u]为满足:存在一条从 s d [ u ] sd[u] sd[u] u u u的路径 s d [ u ] , v 1 , v 2 , . . . , v k , u sd[u],v_1,v_2,...,v_k,u sd[u],v1,v2,...,vk,u,使得 d f n [ v i ] > d f n [ u ] dfn[v_i]>dfn[u] dfn[vi]>dfn[u] d f n dfn dfn最小的点。设u的支配点为 i d [ u ] id[u] id[u],可以证明 i d [ u ] id[u] id[u] s d [ u ] sd[u] sd[u]都是 u u u d f s dfs dfs树上的祖先。

发现如果只有树边,那么支配树就是 d f s dfs dfs树。而每个点的半支配点最大限度地使得它的祖先不能成为它的支配点。于是对问题做一个转化:只保留 d f s dfs dfs树上的树边,再加上每一对 ( s d [ u ] , u ) (sd[u],u) (sd[u],u),新的图的支配树与原图等价。

这样对于新的图就比较好做了:首先它是一个DAG,可以直接按照DAG的做法,但还可以进一步简化。对于一个点 u u u,它的支配点是 s d [ u ] sd[u] sd[u]及其祖先中的一个。如果是 s d [ u ] sd[u] sd[u]的祖先,那么存在一条从根到 u u u跨过 s d [ u ] sd[u] sd[u]的路径,故一定有一条边 ( s d [ w ] , w ) (sd[w],w) (sd[w],w)跨过了 s d [ u ] sd[u] sd[u],直接找 u u u s d [ u ] 中 d f n [ s d [ w ] ] sd[u]中dfn[sd[w]] sd[u]dfn[sd[w]]最小的 w w w即可,如果最小值为 d f n [ s d [ u ] ] dfn[sd[u]] dfn[sd[u]],则 i d [ u ] = s d [ u ] id[u]=sd[u] id[u]=sd[u]

剩下问题在于如何找到半支配点。考虑一个点 u u u s d [ u ] sd[u] sd[u]的情况:可能直接是它的父亲,或者就它的一个祖先经过一些dfn大于它的点连到它。对于后者,找到这些点 d f n dfn dfn最小的点 w w w,那么 s d [ u ] sd[u] sd[u]一定支配了 w w w,且路径上所有 w w w之后的点都是 w w w的后代,可以用 w w w来更新 s d [ u ] sd[u] sd[u]

由于 d f n [ w ] > d f n [ u ] dfn[w]>dfn[u] dfn[w]>dfn[u],于是考虑按照 d f n dfn dfn倒序。对于一个点 u u u,找到所有有边连向它的点 v v v,考虑用这些边来计算 u u u s d [ u ] sd[u] sd[u]。如果 d f n [ v ] < d f n [ u ] dfn[v]<dfn[u] dfn[v]<dfn[u],直接用 v v v更新 s d [ u ] sd[u] sd[u],否则,由于 w w w一定是 v v v的祖先,找到 v v v到根的 s d [ u ] sd[u] sd[u]的最小值即可。之后,把 u u u连向它 d f s dfs dfs树上的父亲,可计算出所有 s d [ u ] = f a [ u ] sd[u]=fa[u] sd[u]=fa[u]的点的 i d [ x ] id[x] id[x]

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,m,hd[N],to[N],nx[N],tt;
void add(int u,int v){ //cout<<"add:"<<u<<" "<<v<<endl;
	nx[++tt]=hd[u]; to[hd[u]=tt]=v;
}
vector<int>fr[N],ux[N];
int dn[N],fa[N],dx[N],sz[N],ct;
void dfs(int u){
	dn[u]=++ct; dx[ct]=u; sz[u]=1;
	for(int e=hd[u];e;e=nx[e]) if(!dn[to[e]]){
		int v=to[e]; fa[v]=u; dfs(v); sz[u]+=sz[v];
	}
}
int id[N],sd[N],mn[N],fu[N],su[N],pt[N];
int find(int u){
	if(u==fu[u]) return u;
	int v=fu[u],w=find(fu[u]);
	if(mn[v]<mn[u]) pt[u]=pt[v],mn[u]=mn[v];
	fu[u]=w;
	return w;
}
vector<int>h[N]; bool vs[N];
int main()
{
	srand(time(0));
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	cin>>n>>m;
	for(int u,v,i=1;i<=m;i++)
		scanf("%d%d",&u,&v),h[u].push_back(v),fr[v].push_back(u);
	for(int i=1;i<=n;i++){
		sort(h[i].begin(),h[i].end());
		int z=unique(h[i].begin(),h[i].end())-h[i].begin();
		for(int j=0;j<z;j++) add(i,h[i][j]);
	}
	dfs(1);
	for(int i=1;i<=n;i++) sd[i]=fu[i]=i,pt[i]=i,mn[i]=dn[i];//cout<<"u="<<i<<" fa="<<fa[i]<<endl;
	for(int i=n;i;i--){
		int u=dx[i],z=fr[u].size();
		//cout<<"u="<<u<<endl;
		for(int j=0;j<z;j++){
			int v=fr[u][j];
			if(dn[v]<dn[u]){
				if(dn[v]<dn[sd[u]]) sd[u]=v;
			}
			else{
				find(v);
				if(mn[v]<dn[sd[u]]) sd[u]=dx[mn[v]];
			}
		}
		//cout<<"sd="<<sd[u]<<endl;
		int v=fa[u]; ux[sd[u]].push_back(i);
		fu[u]=v;
		if(dn[sd[v]]<dn[sd[u]]) mn[u]=dn[sd[v]],pt[u]=v;
		else mn[u]=dn[sd[u]],pt[u]=u;
		int b=dn[u]+sz[u]-1; z=ux[v].size();
		if(!z) continue;
		int l=0,r=z-1;
		while(l<r){
			int mid=(l+r)>>1;
			if(ux[v][mid]<=b) r=mid;
			else l=mid+1;
		}
		if(ux[v][l]>b) l++;
		for(int j=l;j<z;j++){
			int x=dx[ux[v][j]];
			find(x);
			if(mn[x]==dn[sd[x]]) id[x]=sd[x];
			else id[x]=pt[x],vs[x]=1;
		}
	}
	for(int i=1;i<=n;i++) if(vs[dx[i]]) id[dx[i]]=id[id[dx[i]]];
	for(int i=n;i;i--) su[dx[i]]++,su[id[dx[i]]]+=su[dx[i]];
	for(int i=1;i<=n;i++) printf("%d ",su[i]); puts("");
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值