[学习笔记]支配树

〇、前言 ¶

这个东西......我真的是吐了,要是在省选之前把它完善了......可是没多的时间了。

这个东西赶脚见到得很少,但是遇到的时候就很有用了,所以还是写一下。实际上很多部分都是直接宅的,但是加入了一些自己的理解。

壹、这是什么 ¶

一个有向图 \(G=\lang V,E\rang\),给定一个起点 \(s\),假设 \(s\) 到能够访问到其他所有顶点。若去掉某点 \(i\) 后,从 \(s\) 无法访问到 \(j\),则称 \(i\)\(j\) 的支配点。显然支配关系满足传递性,若 \(i\) 支配 \(j\)\(j\) 支配 \(k\),则 \(i\) 支配 \(k\).

什么是支配树呢?

\(s\) 为根节点建一个有向树,使得对于任意节点 \(i\),其树上的祖先均为 \(i\) 的支配点,这样的树称为支配树。任何有向图都存在支配树,前提是从 \(s\) 能够访问到所有顶点。

注意:支配树并不一定是 \(G\) 的树形图。因为有些边不是 \(E\) 中的边。

贰、一些定义与约定 ¶

先给出一些定义:

  1. \(\rm dfs\) 树:从点 \(s\) 出发,对 \(G\) 进行一次 \(\tt dfs\),则可以得到一棵 \(\tt dfs\) 树(下文称其为 \(\rm dfst\)),在 \(\tt dfs\) 时,要根据访问的先后顺序对顶点编号,即给每个顶点打上时间戳 \(\tt dfn\). \(\rm dfst\) 中的边称为树边,不在 \(\rm dfst\) 树中的边称为非树边。非树边又分为前向边、返祖边、横叉边。返祖边即从 \(\rm dfst\) 中一个点出发,连接到它在树上的祖先的边,前向边即从树上的某个点直接连接到它的后代,跨越了被连接点的父亲,横叉边即从一棵子树连接到另一棵子树上去。
  2. 直接支配点 \(idom\):点 \(i\) 的支配点中 \(\tt dfn\) 值最大的点即为点 \(i\) 的直接支配点,换句话说,点 \(i\) 在支配树中的父亲即它的直接支配点。直接支配点一般用 \(idom\) 表示;
  3. 半支配点 \(sdom\)\(\rm semi-dominator\)):顶点 \(u\) 的半支配点定义为 \(sdom_u=\min\{{\tt dfn}_v|\exist (v_0,v_1,v_2,...,v_k),v_0=v,v_k=u,\forall 1\le i\le k-1,{\tt dfn}_v>{\tt dfn}_u\}\),说人话就是所有满足从 \(v\)\(u\) 存在一条所有点的 \(\tt dfn\) 大于 \(u\)\(\tt dfn\) 的路径,除了开头 \(v\) 和结尾 \(u\)\(v\)\(\tt dfn\) 最小的那个。再直观一点就是想象在某两条 \(\tt dfst\) 的子链 \(l,m\) 上,他们的分叉为 \(v\)\(u\in l\)\(l\)\(\tt dfn\)\(m\) 小,如果从 \(m\) 有一条横叉边连向 \(u\),那么就可以说 \(v\) 是一个待选的半支配点,从所有待选半支配点中找出那个 \(\tt dfn\) 最小的,这个点就是 \(u\) 的半支配点,较特别的,若 \(v\rightarrow u\),那么 \(v\) 也是一个待选半支配点;

然后是一些符号的约定,其实就是四个箭头:

  • \(u\rightarrow v\),表示 \(u\) 存在一条边由 \(u\)\(v\)
  • \(u\overset{+}{\rightarrow} v\),表示 \(u\) 存在一条由树边组成的路径到 \(v\),且 \(u\neq v\)
  • \(u\overset{.}{\rightarrow} v\),表示 \(u\) 存在一条由树边组成的路径到 \(v\),允许 \(u=v\)
  • \(u\leadsto v\),表示 \(u\) 存在一条路径到 \(v\)

叁、定理、引理和推论 ¶

遵循 \(5,3,1\) 的数字规律,一个一个给出。

1.1.§ 五大引理 §

1.1.1.直接支配存在引理

对于 \(G=\lang V,E\rang\),除了 \(s\) 以外的所有点都存在 \(idom\).

每个点有时间戳,每个点至少有一个支配点。所以一定有 \(\tt dfn\) 最小的支配点。

我有一个 apple,我有一个 pen,所以我有 apple pen.

1.1.2.直接支配祖先引理

\(\forall u\neq s\)\(idom_u\overset{+}{\rightarrow}u\).

如果 \(idom_u\) 不是 \(u\) 的祖先,那 \(s\) 不是可以直接沿着树边到 \(u\) 而不用经过 \(idom_u\) 了吗......

1.1.3.半支配祖先引理

对于 \(G=\lang V,E\rang\)\(\forall u\neq s\)\(sdom_u\overset{+}{\rightarrow}u\).

根据定义,\(sdom_u\) 有一条由非树边组成的路径到达 \(u\),且路径上的点 \(i\) 满足 \({\tt dfn}_i>{\tt dfn}_u\),又 \({\tt dfn}_{sdom_u}<{\tt dfn}_u\),且在所有满足这个条件的点中, \({\tt dfn}_{sdom_u}\) 最小。

考虑取 \(sdom_u\)\(u\)\(\tt LCA\),记为 \(p\),那么 \(t\) 一定是路径上一点,且 \({\tt dfn}_t\le {\tt dfn}_{sdom_u}<{\tt dfn}_u\),而路径中间的顶点 \(i\),有 \({\tt dfn}_i<{\tt dfn}_u\),所以 \(t\) 只能是 \(sdom_u\).

1.1.4.直接支配最高引理

\(\forall u\neq s\),有 \(idom_u\overset{.}{\rightarrow}sdom_u\).

根据 \(1.1.2\)\(1.1.3\) 可知 \(idom_u\)\(sdom_u\) 位于 \(s\) 到某个 \(\tt dfst\) 某个叶节点的路径上。

所以,只要排除 \(sdom_u\overset{.}{\rightarrow}idom_u\) 即可。

考虑反证法,假设 \(sdom_u\overset{.}{\rightarrow}idom_u\),由 \(sdom_u\) 的定义,不难发现 \(idom_u\) 此时不再支配 \(u\),与定义矛盾,所以原命题得证。

1.1.5.直接支配嵌套引理

\(\forall u\overset{.}{\rightarrow} v\),有 \(u\overset{.}{\rightarrow} idom_v\or idom_v\overset{.}{\rightarrow} idom_u\).

肯定地,\(u,v,idom_u,idom_v\) 一定在 \(\tt dfst\)\(s\) 到某个叶子的路径上。

分情况讨论:

  • \({\tt dfn}_u\le {\tt dfn}_{idom_v}\),则有 \(u\overset{.}{\rightarrow}{\tt dfn}_{idom_v} \overset{.}{\rightarrow}v\)
  • \({\tt dfn}_u>{\tt dfn}_{idom_v}\),则有 \({\tt dfn}_{idom_v}\overset{.}{\rightarrow} u\),此时考察 \(idom_u\)\(idom_v\) 的关系:如果 \(idom_u\overset{.}{\rightarrow}idom_v\),则去掉 \(idom_v\) 还是可以从 \(idom_u\)\(u\) 再到 \(v\),与 \(idom_v\) 定义矛盾,所以只能是 \(idom_v\overset{.}{\rightarrow}idom_u\)

1.2.§ 三大定理 §

1.2.1.封闭定理

\(\forall u\neq s\),若 \(\forall v\;{\rm s.t.}\;sdom_u\overset{+}{\rightarrow} v\overset{.}{\rightarrow} u\) 满足 \({\tt dfn}_{sdom_v}>{\tt dfn}_{sdom_u}\),则 \(idom_u=sdom_u\).

感性证明,可以理解为所有在 \(sdom_u\) 管辖范围内的所有点都无法连出去而被封闭在里面了,那么 \(u\) 就一定被 \(sdom_u\) 支配。

1.2.2.继承定理

\(\forall u\neq s\),若 \(\exist v\;{\rm s.t.}\;sdom_u\overset{+}{\rightarrow} v\overset{.}{\rightarrow} u\and{\tt dfn}_{sdom_v}<{\tt dfn}_{sdom_u}\),设 \(p\) 为所有 \(v\)\({\tt dfn}_{sdom_v}\) 最小的一个,则 \(idom_p=idom_u\).

感性证明一下,可以理解为如果有 \(v\)\(sdom_u\) 连出去了,那么 \(u\) 就摆脱了 \(sdom_u\) 的束缚,但由于他是通过 \(v\) 摆脱束缚的,所以束缚 \(v\) 的点必定束缚 \(u\),因而可以直接继承直接支配点。

1.2.3.半支配定理

\(\forall u\neq s,sdom_u=\min\{v|(v,u)\in E,{\tt dfn}_v<{\tt dfn}_u\cup sdom_p|{\tt dfn}_p>{\tt dfn}_u\and \exist (q,w)\in E,p\overset{.}{\rightarrow}q\}\)

对于第 \(1\) 类,即 \(v|(v,u)\in E,{\tt dfn}_v<{\tt dfn}_u\),这是 \(sdom\) 的特殊情况。

对于第 \(2\) 类,即 \(sdom_p|{\tt dfn}_p>{\tt dfn}_u,\exist (q,w)\in E,p\overset{.}{\rightarrow}q\),其实就是考虑横叉边的情况。

1.3.§ 第一推论——直接支配推论 §

\(\forall u\neq s\),令 \(v\) 为所有满足 \(sdom_u\overset{+}{\rightarrow} v\overset{.}{\rightarrow} u\)\({\tt dfn}_{sdom_v}\)最小的点,那么

\[idom_u= \begin{cases} sdom_v&sdom_u=sdom_v \\ idom_v&{\tt dfn}_{sdom_v}<{\tt dfn}_{sdom_u} \end{cases} \]

其实就是将前两个定理 ruá 在了一起,其实在与上面取等的时候,就是 \(v\) 取到 \(u\) 或者 \(v,u\) 有共同半支配点的时候,即封闭定理的情况。

由这个推论不难看出,对于一个新图 \(G'=\lang V,E'\rang\),其中 \(E'=\{(sdom_i,i)|i\neq s\}\),那么 \(G\)\(G'\) 的支配关系是一样的,因为第二种取等情况,可以一直递归至第一种情况,并且一定有 \(G'\) 是一个 \(\tt DAG\).

肆、建树 ¶

大致分为几种不同的图:

  • 树,自己就是支配树;
  • \(\tt DAG\),则对原图进行拓扑排序,依次确定 \(idom\),设当前点为 \(i\),若 \(j\rightarrow i\),则所有 \(j\)\(\tt LCA\) 就是 \(idom_i\)
  • 有向图,由直接支配推论,我们可以将 \(G\) 转化为 \(G'\),然后在这个 \(\tt DAG\) 上使用拓扑即可建出支配树。或者使用另外一种方法,将所有点的 \(sdom\) 找出来之后,使用推论寻找 \(idom\),这样可以避免寻找 \(\tt LCA\),将 \(\tt LCA\)\(\log\) 变成并查集的一般 \(\mathcal O(n\alpha)\),很少会有变态把并查集卡成 \(\mathcal O(\log)\) 的吧?

最复杂情况即一般有向图,时间复杂度 \(\mathcal O(n\alpha)\)\(\mathcal O(n\log n)\).

伍、参考代码 ¶

使用的 \(\mathcal O(n\log n)\) 的算法,毕竟太懒了......

#include<cstdio>
#include<queue>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;

#define Endl putchar('\n')
typedef long long ll;
template<class T>inline T readin(T x){
	x=0; int f=0; char c;
	while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
	for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
	return f? -x: x;
}

const int maxn=2e5;
const int maxm=3e5;
const int logn=18;

int n, m;

struct graph{
	struct edge{
		int to, nxt;
		edge(){}
		edge(int T, int N): to(T), nxt(N){}
	}e[maxn*2+5];
	int tail[maxn+5], ecnt;
	inline void add_edge(int u, int v){
		e[ecnt]=edge(v, tail[u]); tail[u]=ecnt++;
	}
	graph(){
		memset(tail, -1,  sizeof tail);
		ecnt=0;
	}
}G, rG, dag, rdag, domi_tre;
#define foredge(u, G) for(int i=G.tail[u], v=G.e[i].to; ~i; i=G.e[i].nxt, v=G.e[i].to)

inline void input(){
	n=readin(1), m=readin(1);
	int u, v;
	for(int i=1; i<=m; ++i){
		u=readin(1), v=readin(1);
		G.add_edge(u, v);
		rG.add_edge(v, u);
	}
}

// get the dfst
int dfn[maxn+5], refl[maxn+5], fa[maxn+5], timer;
void dfs(int u){
	refl[dfn[u]=++timer]=u;
	foredge(u, G) if(!dfn[v]){
		dfs(v); fa[v]=u;
		dag.add_edge(u, v); // special sdom
	}
}

// dsu
int pre[maxn+5], minn[maxn+5], sdom[maxn+5];
inline void initial(){
	for(int i=1; i<=n; ++i)
		pre[i]=minn[i]=sdom[i]=i;
}
int findrt(int u){
	if(u==pre[u]) return u;
	int ret=findrt(pre[u]);
	if(dfn[sdom[minn[u]]]>dfn[sdom[minn[pre[u]]]])
		minn[u]=minn[pre[u]];
	return pre[u]=ret;
}

inline void tarjan(){
	initial(); // pay attention
	// enumerate dfn from big to small
	for(int j=n; j>1; --j){
		int u=refl[j], res=j;
		if(!u) continue; // s connot reach u
		foredge(u, rG){
			if(!dfn[v]) continue; // s connot reach v
			// an edge which connect u and the node outside
			if(dfn[v]<dfn[u]) res=min(res, dfn[v]);
			else findrt(v), res=min(res, dfn[sdom[minn[v]]]);
		}
		sdom[u]=refl[res];
		pre[u]=fa[u];
		dag.add_edge(sdom[u], u);
	}
}

int dep[maxn+5], tp[maxn+5][logn+5];
inline int getlca(int u, int v){
	if(dep[u]<dep[v]) swap(u, v);
	for(int j=logn; j>=0; --j) if(dep[tp[u][j]]>=dep[v])
		u=tp[u][j];
	if(u==v) return u;
	for(int j=logn; j>=0; --j) if(tp[u][j]!=tp[v][j])
		u=tp[u][j], v=tp[v][j];
	return tp[u][0];
}
inline void insert_tre(int u){
	int anc=rdag.e[rdag.tail[u]].to;
	foredge(u, rdag) anc=getlca(anc, v);
	dep[u]=dep[anc]+1;
	tp[u][0]=anc;
	domi_tre.add_edge(anc, u);
	for(int j=1; j<=logn; ++j)
		tp[u][j]=tp[tp[u][j-1]][j-1];
}
int in[maxn+5];
queue<int>Q;
inline void topu(){
	for(int u=1; u<=n; ++u){
		foredge(u, dag){
			++in[v];
			rdag.add_edge(v, u);
		}
	}
	for(int i=1; i<=n; ++i) if(!in[i]){
		dag.add_edge(0, i), rdag.add_edge(i, 0);
		++in[i];
	}
	Q.push(0);
	while(!Q.empty()){
		int u=Q.front(); Q.pop();
		foredge(u, dag){
			if(!--in[v]){
				Q.push(v);
				insert_tre(v);
			}
		}
	}
}

int siz[maxn+5];
void dfstre(int u){
	siz[u]=1;
	foredge(u, domi_tre){
		dfstre(v);
		siz[u]+=siz[v];
	}
}

signed main(){
	input();
	dfs(1);
	tarjan();
	topu();
	dfstre(0);
	for(int i=1; i<=n; ++i)
		printf("%d ", siz[i]);
	Endl;
	return 0;
}

陆、容易锅的地方 ¶

这些锅可能出现在我的某次考试中,或者原题检测、或者一些其他的时候......

谁知道呢,反正这些地方容易出锅就对了。

  • 由于 \(\tt tail[]\) 是以 \(-1\) 结尾的,所以有些地方潜伏的 \(\color{violet}{\text{RE}}\)
  • 多组数据的时候,不止清空 \(\tt dfn[]\),还要记得 \(\tt refl[]\)
  • 进行 \(\tt dfs()\) 的时候,记得加上 \(\tt !dfn[v]\)
  • 注意支配树的根是 \(0\),所以支配树的 \(\tt dfn\) 上限不只是 \(n\),而是 \(n+1\)
posted @ 2021-04-17 11:46  Arextre  阅读(743)  评论(0编辑  收藏  举报