支配树学习笔记

Posted y_dove

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了支配树学习笔记相关的知识,希望对你有一定的参考价值。

本文参考自cz_xuyixuan的支配树blog

以下给出若干定义

  • 给定一张有向图,定义起点为 \\(r\\)
  • 定义 \\(x\\)\\(y\\) 的支配点,即 \\(x \\in dom(y)\\),当且仅当删掉 \\(x\\) 后, 从 \\(r\\) 无法到达 \\(y\\),且 \\(x != y\\)
  • 定义 \\(x\\)\\(y\\) 的最近支配点,当且仅当,\\(x \\in dom(y)\\)\\(\\forall w \\in dom(y),w != x\\)\\(w \\in dom(x)\\),以下称为 \\(idom(y) = x\\),亦称为 \\(x\\)\\(y\\)最近支配点
  • 由定义可知,\\(idom(x)\\)\\(x\\) 的连边可以构成一棵树,称为支配树
  • 定义有向图的 \\(dfs\\) 树为 \\(T\\)
  • 以下所有大小关系,均指 \\(dfn\\) 的大小关系,即重定义 \\(<\\) 表示 \\(dfn[x] < dfn[y].\\)
  • 定义 \\(x\\)\\(y\\) 的半支配点,即\\(sdom(y) = \\min\\{x|\\exist x -> v_1 -> v_2 -> ..... v_k ->y,\\forall i \\in [1,k],v_i > y\\}\\),用人话讲就是 \\(x\\) 为满足存在一条除首尾外,经过的点都大于 \\(y\\) 的到达 \\(y\\) 的路径的点中 \\(dfs\\) 序最小的点
  • 因为起点 \\(r\\) 的支配点不存在,所以以下讨论都默认不讨论 \\(r\\)
  • 以下用 \\(a \\rightarrow b\\)表示存在一条 \\(a\\)\\(b\\) 的路径,不经过树边,\\(a -> b\\)表示只经过树边, \\(a\\)\\(b\\) 的所有路径则统称为 \\((a,b) \\in path\\)

以下给出若干定理和引理

  • \\(idom(u)\\) 一定是 \\(u\\) 的祖先(1)

显然

  • \\(idom(u)\\)一定是 \\(sdom(u)\\) 的祖先(2)

\\(proof\\) :考虑反证,若不是,则存在 \\(r -> sdom(u) \\rightarrow u\\)不经过 \\(idom(u)\\),矛盾

  • \\(sdom(u)\\) 一定是 \\(u\\) 的祖先(3)

\\(proof\\) : 考虑反证,首先 \\(sdom(u)\\) 可以是 \\(fa(u)\\) ,所以 \\(sdom(u) \\leq fa(u) < u\\) ,若不是祖先,则必然是其他子树,考虑在 \\(dfs\\) 的过程中 ,若 \\((sdom(u),u) \\in path\\), 那么在遍历到 \\(sdom(u)\\) 的过程中,必然直接遍历到 \\(u\\) ,与 \\(sdom(u) \\rightarrow u\\)矛盾

定理 ( idom 与 sdom 关系定理)
定义: \\(u\\)\\(sdom(w)\\)\\(w\\) 的树的路径中(不包含 \\(sdom(w)\\)), \\(sdom(u)\\) 最小的点,那么有

  • \\(idom(w) = sdom(u)\\) \\((sdom(u) = sdom(w))\\)(4)
  • \\(idom(w) = idom(u)\\) \\((sdom(u) < sdom(w))\\)(5)

\\(proof\\):首先我们考虑对于任意一对 \\((u,x)\\) 如何证明 \\(idom(u) = x\\),我们仅需证明两点

  • \\(x \\in dom(u)\\)
  • \\(\\forall v \\in (x -> u), v != x\\) , 有\\(v \\notin dom(u)\\)

(4) 的证明

\\(sdom(u) = sdom(w)\\),我们考虑 \\(sdom(u)\\) 必然是支配点,假设存在一个点 \\(x\\) 使得 \\(r -> x \\rightarrow o -> w\\), 且 \\(x < u\\),那么 \\(sdom(o) < sdom(u)\\),矛盾

接着我们考虑假设有一个点 \\(y\\)\\(sdom(u) -> u\\)中,且是支配点,显然和引理\\((2)\\)矛盾

(5) 的证明

先证\\(idom(u) \\in dom(w)\\)

考虑反证,若删掉 \\(idom(u)\\) 后,仍然存在 \\((r,w) \\in path\\),那么必然是 \\(r -> x \\rightarrow o -> w\\),且 \\(o > u,x < idom(u)\\),且 \\(sdom(o) = x < sdom(u)\\),矛盾

再证不存在更近的支配点

依然考虑反证,假设存在这样的点,不妨设为 \\(y\\),我们考虑将其删去,继续分类讨论

  • (1),\\(y \\notin (u,w)\\) 那么因为 \\(idom(u)\\) 已经是最近的支配点,那么必然存在 \\((r,u) \\in path\\),又因为存在 \\(u -> w\\),那么必然存在\\((r,w) \\in path\\),矛盾
  • (2),\\(y \\in (u,w)\\),\\(idom(w)\\) 一定是 \\(sdom(w)\\)的祖先,矛盾

那么我们根据这个定理,只需求出 \\(sdom\\) ,就可以在 \\(O(n\\log n)\\)的时间内,求出 \\(idom\\)

考虑 \\(sdom\\) 怎么求

定理 (sdom 的另一种简化定义)(6)

  • \\(S1(w) = \\{(v,w) \\in E,v < w\\}\\)
  • \\(S2(w) = \\{sdom(u)|u > w,\\exist(v,w),(u -> v) \\in T \\}\\)
  • \\(sdom(w) = \\min\\{v|v \\in S1(w) \\cup S2(w)\\}\\)

用人话说,就是我们考虑所有\\((v,w) \\in E\\),如果\\(v < w\\),那么\\(v\\)可以纳入侯选集合,否则,我们考虑其所有祖先\\(u\\)满足\\(u > w\\),\\(sdom(u)\\)可以纳入侯选集合

证明:\\(S1\\)的证明是显然的,我们考虑 \\(S2\\) 怎么证,其实也是显然的,我们考虑这么一条路径,\\(sdom(u) -> u -> v -> w\\),满足条件

有了以上这些定理和引理,我们考虑具体的构造过程

构造过程

大体可以分成两部分。

  • 得到 \\(sdom\\) 数组
  • \\(sdom\\) 得到 \\(idom\\)

以下内容基本参考 cz_xuyixuan 的 blog,先膜拜

算法基本流程

  • 初始化、跑一遍 \\(DFS\\) 得到 \\(T\\).
  • 按标号从大到小求出 \\(sdom\\)
  • 求出所有能确定的 \\(idom\\), 剩下的点记录下和哪个点的 \\(idom\\) 是相同的
  • 按照标号从小到大再跑一次,得到所有点的 \\(idom\\)

第一步显然

第二步和第三步可以一起做,我们可以考虑维护一个带权并查集,每个点维护其到根 \\(sdom\\) 的最小值,按 \\(dfs\\) 序从大到小扫一遍,每当我们计算完 \\(w\\) ,我们可以寻找当前所有 \\(sdom(u) = fa(w)\\) 的点 \\(u\\) , 计算 \\(\\min\\{sdom(v)| v \\in (sdom(u)->u)\\}\\),这个可以用 \\(\\textstyle vector\\) 然后清空掉,容易发现这样维护的只有当前一条链,然后我们可以通过定理 (4), (5) ,来得到 \\(idom(u) = sdom(u)\\) 或者 \\(idom(u) = idom(v)\\),不过我们现在可能还不知道 \\(idom(v)\\) , 所以先打个标记

最后,从小到大扫一遍,得到 \\(idom\\) 数组

代码如下:也就调了一百万年吧

#include<bits/stdc++.h> 
using namespace std;
int read() {
	char c = getchar();
	int x = 0;
	while(c < \'0\' || c > \'9\')	c = getchar();
	while(c >= \'0\' && c <= \'9\')	x = x * 10 + c - 48,c = getchar();
	return x;
}
const int _ = 5e5 + 7;
int sdom[_],idom[_];
vector<int>E[_];
vector<int>O[_];
vector<int>T[_];
vector<int>G[_]; 
int par[_];
#define pb push_back
int n,m;int siz[_]; 
int dfn[_],dfncnt,isdfn[_];
int fa[_];int mn[_];
bool cmp(int u,int v) {
	return dfn[u] < dfn[v];
}
int Min(int u,int v) {
	if(cmp(u,v))	return u;
	return v;
}
int get(int u) {
	if (fa[u] == u)	return u;	
	int tmp = fa[u];	
	fa[u] = get(fa[u]);
	if(dfn[sdom[mn[u]]] > dfn[sdom[mn[tmp]]])		mn[u] = mn[tmp];
	return fa[u];
}
void dfs(int u) {
	dfn[u] = ++dfncnt;isdfn[dfncnt] = u;
	sdom[u] = u;
	for (auto v:E[u]) {
		if(!dfn[v]){
//			cout<<"E"<<\' \'<<u <<\' \'<<v<<\'\\n\';
			par[v] = u;
			dfs(v);
		}
	}
}
void tree(int u) {
	siz[u] = 1;
	for (auto v:T[u]) {
//		cout << u << \' \' <<v<<"hxsb"<<\'\\n\';
		tree(v);
		siz[u] += siz[v];
	}
}
int main() {
	n = read(),m = read();
	for (int i = 1; i <= m; ++i) {
		int u = read(),v = read();
		E[u].pb(v);O[v].pb(u);
	}
	E[0].pb(1);O[1].pb(0);
	dfs(0);
	for (int i = 1; i <= n; ++i)	fa[i] = i;
	for (int i = n + 1; i >= 1; --i) {
		int u = isdfn[i];
		for (auto v:O[u]) {
			if (cmp(v,u)){
				sdom[u] = Min(sdom[u],v);
			}
			else {
				get(v);				
				sdom[u] = Min(sdom[u],sdom[mn[v]]);
			}
		}
		G[sdom[u]].pb(u);
		mn[u] = u;
		for (auto v:E[u]) {
			if(dfn[v] > dfn[u] && par[v] == u) {
				fa[v] = u;
				get(v);
			}
		}
		for (auto v:G[par[u]])  {
//			if(v == 4)	cout<<<<\'\\n\';
			get(v);
			int w = mn[v];
//			if(v == 4)	cout<<w<<\'\\n\';
			if(sdom[w] == sdom[v])	idom[v] = sdom[v];
			else	idom[v] = w;
		}
		get(u);
		G[par[u]].clear();
	}
	for (int i = 1; i <= n + 1; ++i) {
		int u = isdfn[i];
		if (idom[u] == sdom[u])	continue;
		else	idom[u] = idom[idom[u]];
	}		
//	for (int i = 1; i <= n; ++i)	cout<<idom[i]<<\' \';
//	cout<<\'\\n\';
	for (int i = 1; i <= n; ++i)	T[idom[i]].pb(i);
	tree(0);
	for (int i = 1; i <= n; ++i)	printf("%d ",siz[i]);
	cout<<\'\\n\';
	return 0;
}

以上是关于支配树学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

支配树学习日志

支配树学习思路/模板

CodeChef GRAPHCNT

DOM探索之基础详解——学习笔记

支配树

学习笔记:python3,代码片段(2017)