P11967 [GESP202503 八级] 割裂

GESP8 冲刺课堂页:先建模,再压缩状态,最后读代码。

一、先回答问题

结论

可删点必须在坏路径上,同时不能被任何好路径覆盖。

容易误入的路

逐条好路径暴力标记点,n 到 1e6、a 到 1e5 会炸。

二、核心观察

建模与压缩

LCA 确定路径,点差分统计所有好路径覆盖次数 cnt[x]。

为什么不丢答案:树上两点路径唯一,差分还原后 cnt[x]=0 等价于 x 不在任何好路径上。

LCA diff/cnt 坏路径

三、互动演示

步骤1
关键状态 A-
关键状态 B-
答案/目标-
复杂度看表
课堂动作手推

四、自己选答案

问题 1

点差分为什么减 parent(lca)?

问题 2

候选点必须在哪里?

五、手推结果

路径类型要求处理
好路径不能删路径点差分标覆盖
坏路径必须删路径点最后枚举
答案覆盖为 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;
}