其他分享
首页 > 其他分享> > 浅谈支配树(Lengauer - Tarjan Algorithm)

浅谈支配树(Lengauer - Tarjan Algorithm)

作者:互联网

他们迂回误会,我却只由你支配

问世间,哪有更完美

              ---《牵丝戏》    

前言

问题引入

给定单源有向图 \(G\),假定起点为 \(r\) ,对于每个点 \(w\),若存在点 \(d\) 满足删去 \(d\) 后 \(r\) 与 \(w\) 不连通,即所有 \(r\) 到 \(w\) 的路径都经过 \(d\) ,则称 \(d\) 支配 \(w\) , \(d\) 是 \(w\) 的一个支配点,求出每个点能支配多少个点。

支配树简介

如果点 \(w\) 的一个支配点 \(i\) 满足 \(i\) 被 \(w\) 的所有其他支配点支配,则称 \(i\) 为 \(w\) 的最近支配点 ( \(immediate\) \(dominator\) ),记作 \(idom(w)\),容易发现,支配关系定义了支配点集合上的一个全序(反对称性与传递性),因此一个点的 \(idom\) 是唯一的。

对于所有非起点的点 \(w\) , \(idom(w) \to w\) 构成的图 \(G'\) 是一棵树,每个点支配其子树中的所有点,我们称 \(G'\) 为 \(G\) 的支配树。

下面介绍的 Lengauer - Tarjan 算法可以 以 \(O(n\log n + m)\) 的时间,\(O(n + m)\) 的空间求解支配树。

前置知识

Dfs 序相关知识,如果学习过 Tarjan 求强连通分量是更好的。

Dfs 序与 Tarjan(OI Wiki)

记号与约定

定理推导

接下来我们将通过这 \(5\) 条引理来推到一些更有效的结论,解释 \(idom\) 与 \(sdom\) 之间的关系,进而导出求解的算法,请读者再次明确前文的定义,部分证明可能较为复杂,这里给出支配树的一个示例,其中括号内数字为 Dfs 序,字母为半支配点(图源网络,侵删):

类似地,我们可以推导出另一个结论,证明留给读者思考。

经过以上两个定理的推导,我们就可以利用 \(sdom\) 求解 \(idom\) 了,给出一个简单的推论:

目前的主要问题是如何快速计算 \(sdom\) ,直接根据定义计算的效率不能满足我们的要求,于是引入另一个定理:

总结一下,通过一系列偏序关系,我们得到了 \(idom\) 与 \(sdom\) 另一个完备且更为简单的定义,问题就转化为我们熟悉的树上操作了。

算法流程

\(1.\) 求 Dfs 树

\(2.\) 求解半支配点

\(3.\) 利用半支配点求解支配点

其中 \(1\) 是平凡的,先考虑求半支配点,自然地按 Dfs 序倒序枚举,设枚举到的点为 \(u\) ,建出反图,遍历 \(u\) 的前驱,根据定理 \(3\) ,第一种情况可以直接求出,对于第二种情况,我们维护 Dfs 树上的一个带权并查集,每次处理一个点后把他合并到 Dfs 树上的父亲,同时维护一个到祖先的 \(sdom\) 最小值,记为 \(ran\)。

我们来审视一下这部分流程,由于先枚举的先标号更大,所以最新枚举到的点可以视为一个临时的根,那么定理 \(3\) 的情况 \(2\) 可以直接维护,同时,自底向上构建半支配树,对 \(idom\) 的求解也转化为了到根的 \(sdom\) 最值查询,注意到我们可以先处理一部分 \(c(x)\) 中点对 \(x\) 的影响,每次对 \(x\) 当前半支配树的儿子求一遍 \(idom\) 并删去这些儿子,最后由于定理 \(3\) 的情况 \(2\) 会出现不知道某个点的 \(sdom\) 的情况,可以直接令 \(idom\) 指向\(ran\) ,最后正序扫描一遍即可。

代码实现

给出一份较为简洁的代码以供参考:

#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>

const int N = 3e5 + 5;

int n, m, rt;
int dfn[N], p[N], fa[N], idx;
int idom[N], sdom[N], f[N], ran[N], ans[N];

std :: vector <int> e[N], g[N], tr[N];

void Dfs(int u) {
	p[dfn[u] = ++idx] = u;
	for (int v : e[u])
		if (!dfn[v])
			fa[v] = u, Dfs(v);
}

int Merge(int x) {
	if (f[x] == x)
		return x;
	int res = Merge(f[x]);
	if (dfn[sdom[ran[f[x]]]] < dfn[sdom[ran[x]]])
		ran[x] = ran[f[x]];
	return f[x] = res;
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++) {
		int u, v;
		scanf("%d%d", &u, &v);
		e[u].push_back(v);
		g[v].push_back(u);
	}
	Dfs(1);
	for (int i = 1; i <= n; i++)
		sdom[i] = f[i] = ran[i] = i;
	for (int i = idx; i > 1; i--) {
		int tmp = p[i];
		for (int v : g[tmp]) {
			if (!dfn[v])
				continue;
			Merge(v);
			if (dfn[sdom[ran[v]]] < dfn[sdom[tmp]])
				sdom[tmp] = sdom[ran[v]];
		}
		f[tmp] = fa[tmp];
		tr[sdom[tmp]].push_back(tmp);
		tmp = fa[tmp];
		for (int v : tr[tmp]) {
			Merge(v);
			if (tmp == sdom[ran[v]])
				idom[v] = tmp;
			else
				idom[v] = ran[v];
		}
		tr[tmp].clear();
	}
	for (int i = 2; i <= idx; i++) {
		int tmp = p[i];
		if (idom[tmp] ^ sdom[tmp])
			idom[tmp] = idom[idom[tmp]];
	}
	for (int i = idx; i > 1; i--)
		ans[idom[p[i]]] += ++ans[p[i]];
	ans[1]++;
	for (int i = 1; i <= n; i++)
		printf("%d ", ans[i]);
	return 0;
}

一些思考

标签:tmp,sdom,支配,浅谈,Algorithm,int,Tarjan,Dfs,idom
来源: https://www.cnblogs.com/treap/p/15412380.html