支配树

一、前言

黑科技杀我!

想写什么写什么,所以这篇文章除了我,可能不适合其他人学习。

其实就是无情的搬运工,并且懒得搬证明。

而且有些地方个人认为有些问题并自行纠错,有可能我反而搞错了,谨慎阅读。

二、讲解

(零)、前提

存在起点 \(s\) 的连通有向图。

(一)、基本概念

0.符号含义与特别说明

树一般指\(dfs\)树。树边、返祖边、前向边、横叉边也很好理解。

\(u\rightarrow v\):表示存在一条 \(u\)\(v\)

\(u\overset{+}{\rightarrow} v\):表示存在一条 \(u\)\(v\) 的树边且 \(u\not=v\)

\(u\overset{.}{\rightarrow} v\):表示存在一条 \(u\)\(v\) 的树边但允许 \(u=v\)

\(u\leadsto v\):表示存在一条由 \(u\)\(v\)路径

在本文中,\(dfn\)值 和\(dfs\)序等价。

1.最近支配点idom

\(i\) 的支配点中\(dfs\)序最大的点即为点 \(i\) 的最近支配点,换句话说,点 \(i\) 在支配树中的父亲即它的最近支配点。最近支配点一般用 \(idom\) 表示。

2.半支配点sdom

顶点 \(v\) 的半支配点 \(u\) 是所有符合下列条件的点中 \(dfs\)序 最小的点:

  • 顶点 \(i\) 存在一条路径到 \(v\) 且路径上的顶点(不包含两个端点)的\(dfs\)序均大于\(v\)\(dfs\)序。

\(v\) 的半支配点记为 \(sdom(v)\)

特别的,若 \(u\rightarrow v\)\(u\) 也是 \(sdom(v)\) 的候选点。这种情况相当于路径上没有其他点。

(二)、五大引理

1.

\(s\) 以外的每个点存在唯一的 \(idom\)

证明:过于显然,略。

2.

\(\forall w\not=s\),有 \(idom(w)\overset{+}{\rightarrow}w\)

\(idom(w)\) 是树中 \(w\) 的祖先。

证明:反证,考虑如果去掉 \(idom(w)\)\(s\) 可以通过树边到达 \(w\)

3.

\(\forall w\not=s\),有 \(sdom(w)\overset{+}{\rightarrow}w\)

\(sdom(w)\) 是树中 \(w\) 的祖先。

证明:咕。

4.

\(\forall w\not=s\),有 \(idom(w)\overset{.}{\rightarrow}sdom(w)\)

\(idom(w)\) 要么是 $sdom(w) 的祖先,要么是 \(sdom(m)\) 本身。

证明:根据引理二和引理三再反证即可。

5.

对于满足 \(u\overset{.}{\rightarrow}v\) 的点 \(u,v\),则有 \(u\overset{.}{\rightarrow}idom(v)\)\(idom(v)\overset{.}{\rightarrow}idom(u)\)

读者自证不难。

(三)、三项定理

1.

\(\forall u\not=s\),如果对于所有满足 \(sdom(u)\overset{+}{\rightarrow}v\overset{.}{\rightarrow}u\)\(v\),有 \(dfn(sdom(v))\ge dfn(sdom(u))\),则有 \(idom(u)=sdom(u)\)

读者自证不难。

2.

\(\forall u\not=s\),如果有 \(sdom(u)\overset{+}{\rightarrow}v\overset{.}{\rightarrow}u\),设 \(v\) 是满足 \(dfn(sdom(v))\) 最小的一个 \(v\),若 \(dfn(sdom(v)) < dfn(sdom(u))\),则有 \(idom(u)=idom(v)\)

读者自证不难。

3.

读者自证不难。

(四)、重要推论

\(\forall u\not=s\),令 \(u\) 为所有满足 \(sdom(v)\overset{+}{\rightarrow}u\overset{.}{\rightarrow}v\)\(u\)\(dfn(sdom(u))\) 最小的一个点,有:

\[idom(v) \begin{cases} sdom(v) (sdom(u)=sdom(v))\\ idom(u) (dfn(sdom(u))<dfn(sdom(v))) \end{cases}\]

(五)、具体实现

1.\(O(n\log_2n)\)

  • 如果是一棵树,那么它本身就是支配树。

  • 如果是一个 \(DAG\),那么可以拓扑排序,依次确定每个点的 \(idom\)。具体操作为在原图中找出其所有前驱在支配树上的 \(LCA\),这个就是它的 \(idom\)

  • 如果是一个普通有向图,那么我们可以先求出每个点的 \(sdom\),然后删掉所有非树边并连边 \((sdom(u),u)\),此时得到一个 \(DAG\),支配关系与原图一致。

2.\(O(\alpha(n)n))\)

利用推论即可。

三、练习

板题(洛谷)

四、代码

板题 \((O(n\log_2n))\)

//12252024832524
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define TT template<typename T>
using namespace std; 

typedef long long LL;
const int MAXN = 200005;
const int MAXM = 300005 << 1;
int n,m;

LL Read()
{
	LL x = 0,f = 1;char c = getchar();
	while(c > '9' || c < '0'){if(c == '-') f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
	return x * f;
}
TT void Put1(T x)
{
	if(x > 9) Put1(x/10);
	putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
	if(x < 0) putchar('-'),x = -x;
	Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}

struct EDGE
{
	int head[MAXN],tot;
	struct edge
	{
		int v,nxt;
	}e[MAXM];
	void Add_Edge(int x,int y)
	{
		e[++tot].v = y;
		e[tot].nxt = head[x];
		head[x] = tot;
	}
}e,re,eg,reg,dt;//原图(边),反图(边),等价图,反等价图,支配树

int dfn[MAXN],rdfn[MAXN],dfntot,fa[MAXN];
void dfs(int x)
{
	rdfn[dfn[x] = ++dfntot] = x;
	for(int i = e.head[x]; i ;i = e.e[i].nxt)
	{
		int v = e.e[i].v;
		if(dfn[v]) continue;
		fa[v] = x;
		dfs(v); 
		eg.Add_Edge(x,v); 
	}
}
int F[MAXN],sdom[MAXN],MIN[MAXN];
int findSet(int x)
{
	if(x == F[x]) return F[x];
	int ftr = F[x]; F[x] = findSet(F[x]);
	if(dfn[sdom[MIN[x]]] > dfn[sdom[MIN[ftr]]]) MIN[x] = MIN[ftr];
	return F[x];
}
void Tarjan()
{
	for(int i = 1;i <= n;++ i) sdom[i] = MIN[i] = F[i] = i;
	for(int i = n;i > 1;-- i)//枚举的是dfs序
	{
		int x = rdfn[i];//当前更新节点
		if(!x) continue;//不连通
		int ret = i;
		for(int j = re.head[x]; j ;j = re.e[j].nxt) 
		{
			int v = re.e[j].v;
			if(!dfn[v]) continue;
			if(dfn[v] < dfn[x]) ret = Min(ret,dfn[v]);
			else
			{
				findSet(v);
				ret = Min(ret,dfn[sdom[MIN[v]]]); 
			}
		}
		sdom[x] = rdfn[ret];
		F[x] = fa[x];
		eg.Add_Edge(sdom[x],x); 
	}
} 
int d[MAXN],f[MAXN][18];
int lca(int x,int y) 
{
	if(x == y) return x;
	if(d[x] < d[y]) swap(x,y);
	for(int i = 17;i >= 0;-- i)
		if(d[f[x][i]] >= d[y]) x = f[x][i];
	if(x == y) return x;
	for(int i = 17;i >= 0;-- i)
		if(f[x][i] != f[y][i])
			x = f[x][i],y = f[y][i];
	return f[x][0];
}
void bdt(int x)//build dominating tree
{
	int LCA = 0;
	for(int i = reg.head[x]; i ;i = reg.e[i].nxt) 
	{
		int v = reg.e[i].v;
		if(!LCA) LCA = v;
		else LCA = lca(LCA,v);
	}
	f[x][0] = LCA;
	d[x] = d[LCA] + 1;
	dt.Add_Edge(LCA,x);
	for(int i = 1;i <= 17;++ i) f[x][i] = f[f[x][i-1]][i-1];
}
int in[MAXN];
void topu()
{
	for(int x = 1;x <= n;++ x)
		for(int i = eg.head[x]; i ;i = eg.e[i].nxt)
		{
			int v = eg.e[i].v;
			in[v]++;
			reg.Add_Edge(v,x);
		}
	queue<int> q;
	for(int i = 1;i <= n;++ i) if(!in[i]) q.push(i),bdt(i);
	while(!q.empty())
	{
		int x = q.front(); q.pop();
		for(int i = eg.head[x]; i ;i = eg.e[i].nxt)
		{
			int v = eg.e[i].v;
			if(!(--in[v])) q.push(v),bdt(v);
		}
	}
}
int siz[MAXN];
void dtdfs(int x)
{
	siz[x] = 1;
	for(int i = dt.head[x],v; i ;i = dt.e[i].nxt)
		v = dt.e[i].v,dtdfs(v),siz[x] += siz[v];
}

int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n = Read(); m = Read();
	for(int i = 1,u,v;i <= m;++ i) u = Read(),v = Read(),e.Add_Edge(u,v),re.Add_Edge(v,u);
	dfs(1);
	Tarjan();
	topu();
	dtdfs(0);
	for(int i = 1;i <= n;++ i) Put(siz[i],' ');
	return 0;
}

五、吐槽

不会真有人写 \(O(n\log_2n)\) 的算法吧。

posted @ 2021-04-17 16:52  皮皮刘  阅读(226)  评论(0编辑  收藏  举报