可删点必须在坏路径上,同时不能被任何好路径覆盖。
P11967 [GESP202503 八级] 割裂
GESP8 冲刺课堂页:先建模,再压缩状态,最后读代码。
一、先回答问题
逐条好路径暴力标记点,n 到 1e6、a 到 1e5 会炸。
二、核心观察
LCA 确定路径,点差分统计所有好路径覆盖次数 cnt[x]。
为什么不丢答案:树上两点路径唯一,差分还原后 cnt[x]=0 等价于 x 不在任何好路径上。
LCA
diff/cnt
坏路径
三、互动演示
步骤1
关键状态 A-
关键状态 B-
答案/目标-
复杂度看表
课堂动作手推
点击下一步开始。
四、自己选答案
点差分为什么减 parent(lca)?
先判断,再点按钮。
候选点必须在哪里?
先判断,再点按钮。
五、手推结果
| 路径类型 | 要求 | 处理 |
|---|---|---|
| 好路径 | 不能删路径点 | 差分标覆盖 |
| 坏路径 | 必须删路径点 | 最后枚举 |
| 答案 | 覆盖为 0 | 计数 |
课堂要求:每题先说清对象、关系、保留的信息、压缩掉的信息,再写代码。
六、C++14 参考代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1000005;
const int LOGN = 20;
vector<int> adj[MAXN];
int f[MAXN][LOGN];
int dep[MAXN], diff[MAXN], cnt[MAXN];
int n, a;
// 预处理深度和倍增表
void dfs1(int u, int p, int d) {
dep[u] = d;
f[u][0] = p;
for (int i = 1; i < LOGN; i++) {
f[u][i] = f[f[u][i - 1]][i - 1];
}
for (int v : adj[u]) {
if (v != p) dfs1(v, u, d + 1);
}
}
// 求 LCA
int get_lca(int u, int v) {
if (dep[u] < dep[v]) swap(u, v);
for (int i = LOGN - 1; i >= 0; i--) {
if (dep[f[u][i]] >= dep[v]) u = f[u][i];
}
if (u == v) return u;
for (int i = LOGN - 1; i >= 0; i--) {
if (f[u][i] != f[v][i]) {
u = f[u][i];
v = f[v][i];
}
}
return f[u][0];
}
// 统计差分结果
void dfs2(int u, int p) {
cnt[u] = diff[u];
for (int v : adj[u]) {
if (v != p) {
dfs2(v, u);
cnt[u] += cnt[v];
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
if (!(cin >> n >> a)) return 0;
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
adj[u].push_back(v);
adj[v].push_back(u);
}
dfs1(1, 0, 1);
for (int i = 0; i < a; i++) {
int u, v;
cin >> u >> v;
int lca = get_lca(u, v);
int plca = f[lca][0];
diff[u]++;
diff[v]++;
diff[lca]--;
diff[plca]--;
}
dfs2(1, 0);
int bu, bv;
cin >> bu >> bv;
int blca = get_lca(bu, bv);
int ans = 0;
// 检查从 bu 到 blca 的路径
int curr = bu;
while (curr != blca) {
if (cnt[curr] == 0) ans++;
curr = f[curr][0];
}
// 检查从 bv 到 blca 的路径
curr = bv;
while (curr != blca) {
if (cnt[curr] == 0) ans++;
curr = f[curr][0];
}
// 检查 blca 节点本身
if (cnt[blca] == 0) ans++;
cout << ans << endl;
return 0;
}