[学习笔记]支配树

〇、前言 ¶

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

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

壹、这是什么 ¶

一个有向图 \(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  阅读(1064)  评论(0)    收藏  举报