向上和向下都是确定移动,分别预处理倍增表快速跳很多步。
P13019 [GESP202506 八级] 树上旅行
GESP8 冲刺课堂页:先建模,再压缩状态,最后读代码。
一、先回答问题
按 a 的绝对值一步步模拟,单次移动可能达到 n。
二、核心观察
up[u][k] 表示向上 2^k 步,down[u][k] 表示沿最小孩子链向下 2^k 步。
为什么不丢答案:每种移动从当前点出发结果唯一,因此可以像函数复合一样倍增。
当前位置
up 表
down 表
三、互动演示
步骤1
关键状态 A-
关键状态 B-
答案/目标-
复杂度看表
课堂动作手推
点击下一步开始。
四、自己选答案
根节点向上过多怎么办?
先判断,再点按钮。
叶子向下过多怎么办?
先判断,再点按钮。
五、手推结果
| 移动 | 倍增表 | 边界 |
|---|---|---|
| 向上 | 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;
}