支配树总结
支配树总结
相关概念
支配:对于一个给定的起点\(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\)会到达\(P\cup 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 << '\n';
// }
dfs(1, -1);
for(int i = 1; i <= n; i++) cout << sz[i] << ' ';
return 0;
}
重要的是自信,一旦有了自信,人就会赢得一切。