Notes for 支配树 (Dominator Tree?)

(有些细节被 skip 了,待填)

徐老师的 blog(都是严格证明,但是菜如我的话看着可能会晕)(大雾)

你也可以感性理解,就像这篇 blog 一样

考虑一张有向图 G = (V, E),有起点 s \in V,满足 \forall v \in V存在一条 s 到 v 的路径。对每个点 v,我们称节点 u(u \not= v) 支配节点 v 当且仅当任意一条 s 到 v 的路径都经过 u。要对每个结点 v 求出有哪些节点支配它。

注意到如果节点 u 和 v 都支配 w 且 u \not= v,那么 u 和 v 之间一定有支配关系。因此,支配的关系构成了一个有向树的结构,我们称之为支配树。

支配树可以在 O(n \log (n)) 时间内求出。 idom(u) 其中 u 是路径上 sdom(u) 最浅的节点。

 

DAG 支配树

没什么好说的。v 的 parent 是 v 的所有前驱在支配树上的 LCA,倍增 + DP 即可。

 

任意有向图支配树

s 为根先找到任意一棵 DFS 树。边被分成了几种:树边、回向边、前向边、横叉边。显然支配 v 的点必然是 v 在 DFS 树上的祖先。

令 sdom(v) 表示 v 距离它最近的满足以下条件的祖先:【任意从 s 到 v 的路径都必须经过一个 从 sdom(v) 到 v 的只经过树边的路径上的异于 v 的点】。 

sdom(v) 的一个等价定义:距离 v 最远的满足【存在一条从 sdom(v) 到 v 只经过 DFS 先序遍历在  v  右侧的点的路径】的最先。这个定义可以方便求出 sdom(v):考虑上一条边是不是横叉边——所有 v 的前驱和所有 v DFS 先序比它大的前驱的祖先的 sdom 和取 min 即可。

这个过程需要一个数据结构支持加边和求到根的路径的 min。可以用带权并查集维护。

接下来我们考虑怎么用 sdom(v) 求出 v 在支配树上的 parent,记为 idom(v)。注意到原图在支配树意义下相当于删去所有非树边然后将 sdom(v) 到 v 的树上路径向 v 连边。因此 idom(v) 就是 sdom(v) 到 v 的树上路径在支配树上的 LCA。

进一步地,如果 sdom(v) 到 v 上所有结点的 sdom 都是 sdom(v)  的子孙,那么 idom(v)  就是 sdom(v) ;否则,它就是

idom(u) 其中 u 是 sdom(v) 到 v 的树上路径上 sdom(u) 最浅的节点:我们要求的相当于是沿着 sdom(v) 到 v 的树上路径的反方向跳必然经过的结点,而从 v 能跳到的点必然能从这个 u 跳到。

和上面使用同一个带权并查集即可。

 

模板题 HDU 4694

注意这题其实并不保证起点能抵达所有点:不能抵达的点答案在本题中被认为是 0

#include <bits/stdc++.h>
#define rep(i, n) for(int i = 0; i < (int)(n); i ++)
#define rep1(i, n) for(int i = 1; i <= (int)(n); i ++)
#define MP make_pair

using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int MOD = 998244353;

int n, m, s;
bool vis[200005];
vector<int> G[200005], RG[200005];
int dfn[200005], did[200005], cnt;
int sdom[200005], idom[200005];
vector<int> T[200005];
vector<int> hv[200005];
int dpar[200005], dval[200005];

void m_dsu(int v, int u)
{
	dpar[v] = u;
}

int q_dsu(int v)
{
	if(dpar[v] == v) return dval[v];
	int rval = q_dsu(dpar[v]);
	if(sdom[dval[v]] > sdom[rval]) dval[v] = rval;
	dpar[v] = dpar[dpar[v]];
	return dval[v];
}

void dfs0(int v)
{
	vis[v] = true;
	did[v] = cnt;
	dfn[cnt] = v;
	cnt ++;
	rep(i, G[v].size()) {
		int u = G[v][i];
		if(vis[u]) continue;
		T[v].push_back(u);
		dfs0(u);
	}
}

void dfs1(int v)
{
	for(int i = T[v].size() - 1; i >= 0; i --) {
		int u = T[v][i];
		dfs1(u);
	}
	sdom[v] = did[v];
	rep(i, RG[v].size()) {
		int u = RG[v][i];
		if(!vis[u]) continue;
		if(did[u] > did[v]) sdom[v] = min(sdom[v], sdom[q_dsu(u)]);
		else sdom[v] = min(sdom[v], did[u]);
	}
	dval[v] = v;
	hv[dfn[sdom[v]]].push_back(v);
	rep(i, hv[v].size()) {
		int u = hv[v][i], cu = q_dsu(u);
		if(sdom[cu] == sdom[u]) idom[u] = sdom[u];
		else idom[u] = ~cu;
	}
	rep(i, T[v].size()) m_dsu(T[v][i], v);
}

void dfs2(int v)
{
	if(idom[v] < 0) idom[v] = idom[~idom[v]];
	rep(i, T[v].size()) dfs2(T[v][i]);
}

LL dp[200005];

bool solve()
{
	if(scanf("%d%d", &n, &m) != 2) return false; 
	rep1(i, n) {
		G[i].clear();
		T[i].clear();
		hv[i].clear();
		RG[i].clear();
	}
	cnt = 0;
	rep(i, m) {
		int u, v;
		scanf("%d%d", &u, &v);
		G[u].push_back(v);
		RG[v].push_back(u);
	}
	s = n;
	rep1(i, n) vis[i] = false;
	dfs0(s);
	rep1(i, n) {
		dpar[i] = i;
		dval[i] = i;
	}
	dfs1(s);
	dfs2(s);
	rep1(i, n) idom[i] = dfn[idom[i]];
	
	rep1(i, n) dp[i] = 0;
	rep(i, cnt) if(i == 0) dp[dfn[i]] = n;
	else dp[dfn[i]] = dp[idom[dfn[i]]] + dfn[i];
	rep1(i, n) printf("%I64d%c", dp[i], " \n"[i == n]);
	return true;
}

int main()
{
	while(solve());
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值