CC 采用 $\text{CC BY-NC-SA 4.0}$ 许可,转载请注明:“转自 【算法】支配树”
引理5:任意 $idom(u)$ 到 $u$ 的路径两两不交(于非端点的点)。
推论(必经点定理):对最小的满足 $u \leq w < sdom(u)$ 且 $sdom(u)$ 是 $w$ 的祖先的点 $w$,有:
定理4(半必经点定理):
PPT中的表述如下:
对于𝐺中的一点𝑦,考虑所有𝑥∈𝑝𝑟𝑒(𝑦),定义一个临时变量𝑡𝑒𝑚𝑝=+INF。
若𝑑𝑓𝑛[𝑥]<𝑑𝑓𝑛[𝑦],𝑡𝑒𝑚𝑝=min(𝑡𝑒𝑚𝑝,𝑑𝑓𝑛[𝑥])。
若𝑑𝑓𝑛[𝑥]>𝑑𝑓𝑛[𝑦],对任意𝑥在𝑇中的祖先𝑧,满足𝑑𝑓𝑛[𝑧]>𝑑𝑓𝑛[𝑦]时,
有𝑡𝑒𝑚𝑝=min(𝑡𝑒𝑚𝑝,𝑑𝑓𝑛[𝑠𝑒𝑚𝑖[𝑧]])。
𝑠𝑒𝑚𝑖[𝑦]=𝑖𝑑[𝑡𝑒𝑚𝑝]。即在上述所有可能情况中取𝑑𝑓𝑛值最小的一种,就是𝑦的半必经点。
代码实现细节如下:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int n, m, S;
int dfn[N], ID[N], fa[N], timer;
int p[N], semi[N], idom[N], home[N];
vector <int> E[N], E_rev[N], Vs[N];
int res[N];
int find(int x) {
if(p[x] < 0) return x;
int px = find(p[x]);
home[x] = getmin(home[x], home[p[x]]);
return p[x] = px;
}
inline void onion(int x, int y) {
x = find(x), y = find(y);
if(x == y) return ;
home[x] = getmin(home[x], home[y]);
p[x] = y;
}
void dfs(int u, int pre) {
dfn[u] = ++timer;
ID[timer] = u;
fa[u] = pre;
for(auto v: E[u]) if(dfn[v] == 0) dfs(v, u);
}
void clear() {
for(int i = 1; i <= n; i++) {
E[i].clear();
E_rev[i].clear();
Vs[i].clear();
idom[i] = fa[i] = home[i] = 0;
}
}
void init() {
int u, v;
for(int i = 1; i <= m; i++) {
scanf("%d%d", &u, &v);
E[u].push_back(v);
E_rev[v].push_back(u);
}
S = n;
timer = 0;
for(int i = 1; i <= n; i++) dfn[i] = 0, p[i] = -1, semi[i] = home[i] = i;
dfs(S, 0);
for(int i = timer; i >= 2; i--) {
u = ID[i];
for(auto v: E_rev[u])
if(dfn[v]) semi[u] = semi[getmin(u, gethome(v))]; //key1 注意这里的写法
Vs[semi[u]].push_back(u);
for(auto v: Vs[fa[u]]) idom[v] = gethome(v); //key2
Vs[fa[u]].clear();
onion(u, fa[u]);
}
res[S] = S;
for(int i = 2; i <= timer; i++) {
u = ID[i];
if(semi[idom[u]] == semi[u]) idom[u] = semi[u]; //key3 注意是semi相等,不一定是idom[u]==u
else idom[u] = idom[idom[u]]; //key4
res[u] = res[idom[u]] + u;
}
for(int i = 1; i <= n; i++) {
if(dfn[i] == 0) printf("0");
else printf("%d", res[i]);
if(i == n) printf("\n");
else printf(" ");
}
clear();
}
int main() {
while(scanf("%d%d", &n, &m) != EOF) init();
return 0;
}
Author : Fanfaredash