P13019 [GESP202506 八级] 树上旅行

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

一、先回答问题

结论

向上和向下都是确定移动,分别预处理倍增表快速跳很多步。

容易误入的路

按 a 的绝对值一步步模拟,单次移动可能达到 n。

二、核心观察

建模与压缩

up[u][k] 表示向上 2^k 步,down[u][k] 表示沿最小孩子链向下 2^k 步。

为什么不丢答案:每种移动从当前点出发结果唯一,因此可以像函数复合一样倍增。

当前位置 up 表 down 表

三、互动演示

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

四、自己选答案

问题 1

根节点向上过多怎么办?

问题 2

叶子向下过多怎么办?

五、手推结果

移动倍增表边界
向上up根停止
向下down叶子停止
序列逐段处理当前位置更新

课堂要求:每题先说清对象、关系、保留的信息、压缩掉的信息,再写代码。

六、C++14 参考代码

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 100005;
const int LOGN = 18;

int up[MAXN][LOGN];
int down[MAXN][LOGN];
int min_child[MAXN];
int dep[MAXN];
int max_down[MAXN];

void dfs_up(int u, int d) {
    dep[u] = d;
    for (int i = 1; i < LOGN; i++) {
        up[u][i] = up[up[u][i - 1]][i - 1];
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, q;
    cin >> n >> q;

    // 初始化最小子节点为无穷大
    for (int i = 1; i <= n; i++) min_child[i] = 0;

    for (int i = 2; i <= n; i++) {
        int p;
        cin >> p;
        up[i][0] = p;
        if (min_child[p] == 0 || i < min_child[p]) {
            min_child[p] = i;
        }
    }

    // 预处理向上倍增和深度
    dep[1] = 0;
    up[1][0] = 1; // 根节点的父节点设为自己
    // 这里按编号顺序处理即可,因为父节点编号不一定小于子节点
    // 但题目给出的是 p_2...p_n,说明 1 是根,按层序或者简单 DFS 更好
    // 既然 p_i 可能大于 i,我们建图做 DFS
    vector<vector<int>> children(n + 1);
    for(int i = 2; i <= n; i++) children[up[i][0]].push_back(i);
    
    // 用队列做 BFS 确定深度和倍增,防止递归过深
    queue<int> que;
    que.push(1);
    while(!que.empty()){
        int u = que.front(); que.pop();
        for(int v : children[u]){
            dep[v] = dep[u] + 1;
            for(int i = 1; i < LOGN; i++) up[v][i] = up[up[v][i-1]][i-1];
            que.push(v);
        }
    }

    // 预处理向下倍增
    // 为了处理 max_down,我们按深度从大到小处理点
    vector<int> nodes(n);
    iota(nodes.begin(), nodes.end(), 1);
    sort(nodes.begin(), nodes.end(), [&](int a, int b) {
        return dep[a] > dep[b];
    });

    for (int u : nodes) {
        if (min_child[u] == 0) {
            down[u][0] = u;
            max_down[u] = 0;
        } else {
            down[u][0] = min_child[u];
            max_down[u] = max_down[min_child[u]] + 1;
        }
        for (int i = 1; i < LOGN; i++) {
            down[u][i] = down[down[u][i - 1]][i - 1];
        }
    }

    // 处理旅行
    while (q--) {
        int s, k;
        cin >> s >> k;
        int curr = s;
        for (int i = 0; i < k; i++) {
            int a;
            cin >> a;
            if (a > 0) {
                int steps = min(a, dep[curr]);
                for (int j = LOGN - 1; j >= 0; j--) {
                    if ((steps >> j) & 1) curr = up[curr][j];
                }
            } else if (a < 0) {
                int steps = min(-a, max_down[curr]);
                for (int j = LOGN - 1; j >= 0; j--) {
                    if ((steps >> j) & 1) curr = down[curr][j];
                }
            }
        }
        cout << curr << "\n";
    }

    return 0;
}