【学习笔记】支配树
先对自己说句话:你觉得没用的算法不一定没用,别太自以为是在那里一遍一遍叫 "stop learning useless algorithm",最 useless 的是你。
支配
给定一个有向图
首先我们将
我们容易发现支配存在传递关系:如果
反证,假如
不支配 ,那么就说明存在一条 的路径且不经过 ,这与 支配 矛盾。
类似的,支配还有以下性质:
- 若
支配 , 支配 ,则 ;反证,假如
,那么说明 且 ,那么说明 的路径中一定出现了环,而显然路径可以不出现环,矛盾。 - 若
互不相等,且 支配 , 支配 ,那么 支配 或 支配 。反证,假如
与 不存在支配关系,那么存在路径 和 ,而 支配 说明 ,这说明存在路径 和 ,这与 支配 和 支配 矛盾。
我们设
容易发现,对于每个
那么,我们从
根据传递性,说明假如在这棵树上
DAG 上的支配树
例题:[ZJOI2012]灾难
考虑在 DAG 上的支配关系:假如某个点
反证,如果
不支配 的某个前驱 ,那么存在路径 ,与 支配 矛盾。
那么我们考虑对 DAG 进行拓扑排序,那么对于点
我们可以使用倍增求 LCA,这样就能动态加点来构造支配树了。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 70005;
int n;
vector<int> e[MAXN], re[MAXN];
int deg[MAXN];
int idom[MAXN];
int fa[MAXN][20];
int dep[MAXN];
int lca(int u, int v) {
if (!u || !v) return u + v;
if (dep[u] < dep[v]) swap(u, v);
for (int i = 16; i >= 0; i--)
if (dep[fa[u][i]] >= dep[v]) u = fa[u][i];
if (u == v) return v;
for (int i = 16; i >= 0; i--)
if (fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
return fa[u][0];
}
vector<int> t[MAXN];
int siz[MAXN];
void dfs(int u) {
siz[u] = 1;
for (int v : t[u])
dfs(v), siz[u] += siz[v];
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
int f; scanf("%d", &f);
while (f) {
e[f].push_back(i);
re[i].push_back(f);
scanf("%d", &f);
}
if (re[i].size() == 0) {
re[i].push_back(n + 1);
e[n + 1].push_back(i);
}
deg[i] = re[i].size();
}
queue<int> q;
q.push(n + 1); idom[n + 1] = n + 1, dep[n + 1] = 1;
while (!q.empty()) {
int u = q.front(); q.pop();
// printf("%d: \n", u);
for (int v : re[u]) {
// printf("pre %d %d\n", u, v);
idom[u] = lca(idom[u], v);
}
dep[u] = dep[idom[u]] + 1;
fa[u][0] = idom[u];
for (int i = 1; i <= 16; i++)
fa[u][i] = fa[fa[u][i - 1]][i - 1];
for (int v : e[u]) {
deg[v]--;
if (!deg[v]) q.push(v);
}
}
for (int i = 1; i <= n; i++) {
// printf("idom[%d]=%d\n", i, idom[i]);
t[idom[i]].push_back(i);
}
dfs(n + 1);
for (int i = 1; i <= n; i++) {
printf("%d\n", siz[i] - 1);
}
return 0;
}
一般图支配树
我们首先给这个图跑出来一个 DFS 树。设
定义一个点
首先可以发现,
对于
考虑如何维护
跳父亲显然不可以接受,我们考虑按照 DFS 序从大到小枚举,然后直接维护每个点到父亲节点的最小值。可以使用并查集来维护这个东西,在路径压缩的时候与新连接的父亲的权值取
主要部分代码:
int find(int x) {
if (f[x] == x) return x;
int fx = f[x];
f[x] = find(f[x]);
val[x] = min(val[x], val[fx]);
return f[x];
}
void solve(int s) {
dfs(s);
for (int i = 1; i <= n; i++)
f[i] = i, val[i] = dfn[i];
for (int i = n; i >= 2; i--) {
int u = idf[i];
for (int v : pre[u]) {
find(v);
val[u] = min(val[u], val[v]);
}
sdom[u] = idf[val[u]];
f[u] = fa[u];
}
for (int i = 1; i <= n; i++) if (i != s) {
dag.add(sdom[i], i);
dag.add(fa[i], i);
}
}
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200005;
int n, m;
int idom[MAXN];
int sdom[MAXN];
struct DAG {
vector<int> to[MAXN], pre[MAXN];
int deg[MAXN];
int fa[MAXN][20];
int dep[MAXN];
int lca(int u, int v) {
if (!u || !v) return u + v;
if (dep[u] < dep[v]) swap(u, v);
for (int i = 16; i >= 0; i--)
if (dep[fa[u][i]] >= dep[v]) u = fa[u][i];
if (u == v) return v;
for (int i = 16; i >= 0; i--)
if (fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
return fa[u][0];
}
void add(int x, int y) {
to[x].push_back(y), pre[y].push_back(x), deg[y]++;
}
void topo(int s) {
queue<int> q;
q.push(s); dep[s] = 1;
while (!q.empty()) {
int u = q.front(); q.pop();
for (int v : pre[u]) {
idom[u] = lca(idom[u], v);
}
dep[u] = dep[idom[u]] + 1;
fa[u][0] = idom[u];
for (int i = 1; i <= 16; i++)
fa[u][i] = fa[fa[u][i - 1]][i - 1];
for (int v : to[u]) {
deg[v]--;
if (!deg[v]) q.push(v);
}
}
}
} dag;
struct Graph {
vector<int> to[MAXN], pre[MAXN];
void add(int x, int y) {
to[x].push_back(y), pre[y].push_back(x);
}
int fa[MAXN];
int f[MAXN], val[MAXN];
int find(int x) {
if (f[x] == x) return x;
int fx = f[x];
f[x] = find(f[x]);
val[x] = min(val[x], val[fx]);
return f[x];
}
int dfn[MAXN], idf[MAXN], dcnt;
void dfs(int u) {
dfn[u] = ++dcnt, idf[dcnt] = u;
for (int v : to[u]) if (!dfn[v]) {
dfs(v);
fa[v] = u;
}
}
void solve(int s) {
dfs(s);
for (int i = 1; i <= n; i++)
f[i] = i, val[i] = dfn[i];
for (int i = n; i >= 2; i--) {
int u = idf[i];
for (int v : pre[u]) {
find(v);
val[u] = min(val[u], val[v]);
}
sdom[u] = idf[val[u]];
f[u] = fa[u];
}
for (int i = 1; i <= n; i++) if (i != s) {
dag.add(sdom[i], i);
dag.add(fa[i], i);
}
}
} g;
vector<int> t[MAXN];
void construct(int s) {
g.solve(s);
dag.topo(s);
for (int i = 1; i <= n; i++) if (i != s) {
t[idom[i]].push_back(i);
}
}
int siz[MAXN];
void dfs(int u) {
siz[u] = 1;
for (int v : t[u])
dfs(v), siz[u] += siz[v];
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
int u, v; scanf("%d%d", &u, &v);
g.add(u, v);
}
construct(1);
dfs(1);
for (int i = 1; i <= n; i++) {
printf("%d ", siz[i]);
}
return 0;
}
另外一个做法:根据一通分析,可以得出一个结论:
对于每个点
- 若
,那么 ; - 否则
。
证明太长了,真的看不下去了,咕了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~