支配树总结
相关概念
支配:对于一个给定的起点(r),当(u)是所有到(v)路径的必经点时,则称(u)支配(v)。
半必经点:不严谨地讲其含义为在(x)的祖先中,能通过非搜索树边而到达(x)并且深度最小的点,记为(semi(x))。
必经点:记(idom(x))表示所求深度最大的必经点。
最终我们希望求出一颗树,在这颗树中,任意结点都支配其子树,并且被其父亲到父亲的父亲等等支配。
性质
(以下对点比较大小关系时,都是对点的(dfn)进行比较的)
- (idom(x)<=semi(x))
这个根据相关定义很好证明,如果(idom(x)>semi(x)),那么能够存在一条路径绕过(idom(x))。
- 设点集(P)为(semi(x)->x)路径上不包含两端点的点的集合,并且(t)是点集(P)中(semi)值最小的点。则有:若(semi(t)=semi(x)),则(idom(x)=semi(x));否则,(idom(x)=idom(t)).
这个证明就要稍微复杂一些。
首先证明第一种情况,如果此时(idom(x)!=semi(x)),根据第一个性质,只有(idom(x)<semi(x)),也就是存在一个点(z),能够绕开(semi(x))到达(x)。那么此时肯定(z)会到达(Pcup x),此时与(semi(t))最小或者(semi(x))最小不符。
第二种情况的话,因为(semi(t)!=semi(x)),那我们分情况考虑:如果(semi(t)>semi(x)),脑补一下这种情况不会发生的;那么就只用考虑(semi(t)<semi(x))的情况。
首先证明(idom(t))是必经点:如果不是,那么说明有点(z)能够绕过(idom(t))到达(t),因为(idom(t)<=semi(t)),而(z<idom(t)),那么(semi(t))就会变小,与假设不符。
其次证明(idom(t))深度最大,如果存在一个深度更大的必经点(y),因为我们首先会到达(idom(t)),之后直接从(idom(t))到(t)最后到(x)就行了,也就是说我们可以直接绕过(y)。
因为(y)点的深度一定是不小于(semi(x))的,而(t)在(semi(x))到(x)的路径上。
有了第二个性质,那么我们就可以根据(semi(x))以及相关路径上面的(semi)值来求(idom)值了。
实现
大概说下怎么求出(semi(x))吧。
对于所有的((u,x)),如果(u<x),则(semi(x)=min(semi(x),dfn(u)));否则与(u)的一系列满足(dfn>dfn(x))的祖先(semi)的最小值进行比较。
第一种情况根据定义显然(u)也是一个半必经点,第二种情况可以脑补一下。
具体实现:
- (dfs)求出(dfn,fa)数组
- 按(dfn)从大到小的顺序进行更新,假设现在求(semi(x)),在反图上访问所有与(x)相邻的(u),按照上面所说的进行更新。
- (x)与(fa[x])进行合并,并令(x=fa[x]),利用第二个性质求出(idom(x))。
- 回到第二步重复此流程
- 填坑(idom(x)!=semi(x))的残余结点
细节说明:
代码其实还是有许多细节的,我就说说我想到的。
更新的时候,更新(semi(x))要用到(dfn>dfn(x))的点;更新(idom(x))的时候需要(idom(x)->x)这条路径上面的(semi)信息。所以我们就直接用带权并查集来维护,我们从大到小枚举(dfn)可以保证其正确性。
更新完(semi(x))之后,讲(x)与(fa[x])合并,之后求(idom(x)),一开始我这里有点疑惑为什么不先求了再合并,因为性质二中说了不要两端点的,后来发现有这样一个不等式(min{semi}leq semi(x)),所以这里先合并也不影响因为(fa[x])的值没有更新,也可以之后再合并。
还有一些细节应该就是建图吧,各种图要区分清楚。
代码
以模板题为例
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5, M = 3e5 + 5;
namespace LT{
vector <int> G[N], rG[N];
vector <int> dt[N]; //dominant tree
int fa[N], best[N], T, n;
int semi[N], idom[N], dfn[N], idx[N], f[N];
void init() {
T = 0;
for(int i = 1; i <= n; i++) semi[i] = f[i] = best[i] = i;
for(int i = 1; i <= n; i++) dt[i].clear();
}
void dfs(int u) {
dfn[u] = ++T; idx[T] = u;;
for(auto v : G[u]) {
if(!dfn[v]) {
fa[v] = u; dfs(v);
}
}
}
int find(int x) {
if(f[x] == x) return x;
int fx = find(f[x]);
if(dfn[semi[best[f[x]]]] < dfn[semi[best[x]]]) best[x] = best[f[x]];
return f[x] = fx;
}
void Tarjan(int rt) {
dfs(rt);
for(int i = T; i >= 2; i--) {
int x = idx[i];
for(int &u : rG[x]) {
if(!dfn[u]) continue; //可能原图不能到达
find(u);
if(dfn[semi[x]] > dfn[semi[best[u]]]) semi[x] = semi[best[u]];
}
f[x] = fa[x];
dt[semi[x]].push_back(x);
x = fa[x];
for(int &u : dt[x]) {
find(u);
if(semi[best[u]] != x) idom[u] = best[u];
else idom[u] = x;
}
dt[x].clear();
}
for(int i = 2; i <= T; i++) {
int x = idx[i];
if(idom[x] != semi[x]) idom[x] = idom[idom[x]];
dt[idom[x]].push_back(x);
}
}
}
int n, m;
int sz[N];
void dfs(int u, int fa) {
sz[u] = 1;
for(auto v : LT::dt[u]) {
if(v == fa) continue;
dfs(v, u);
sz[u] += sz[v];
}
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> n >> m;
LT::n = n;
LT::init();
for(int i = 1; i <= m; i++) {
int u, v; cin >> u >> v;
LT::G[u].push_back(v);
LT::rG[v].push_back(u);
}
LT::Tarjan(1);
// for(int i = 1; i <= n; i++) {
// cout << i << ':' ;
// for(auto x : LT::dt[i]) cout << x << ' ';
// cout << '
';
// }
dfs(1, -1);
for(int i = 1; i <= n; i++) cout << sz[i] << ' ';
return 0;
}