P13019 [GESP202506 八级] 树上旅行
https://www.luogu.com.cn/problem/P13019
💡算法总体思路
- \(f[i][k]\) :从节点 \(i\) **向上跳 \(2^k\) ** 步后到达的节点编号。
- \(g[i][k]\) :从节点 \(i\) **沿最小编号子链向下跳 \(2^k\) ** 步后到达的节点编号。
- \(h\) :指针变量,用来在
f和g之间切换(正数向上走用f,负数向下走用g)。 - 输入序列每个 \(a_j\) :
- 若为正 → 向上跳 \(a_j\) 步
- 若为负 → 向下跳 \(-a_j\) 步
用 倍增表 实现快速跳跃,每次跳 \(O(\log n)\)
📘详细逐行注释版代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010; // 最大节点数 n <= 1e5
const int M = 17; // log2(1e5) ≈ 17,倍增层数
int n, m; // n = 节点数, m = 查询数
int f[N][M]; // f[i][k] = 从 i 向上跳 2^k 步后的节点
int g[N][M]; // g[i][k] = 从 i 沿最小子节点链向下跳 2^k 步后的节点
int (*h)[M]; // h 是一个“二维数组指针”,可指向 f 或 g(即在二者间切换)
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
// ----------- 建树输入与最小子节点初始化 -----------
for (int i = 2; i <= n; i ++ )
{
int p; // p 是节点 i 的父亲
cin >> p;
f[i][0] = p; // 倍增表第一层:父节点
// 更新父节点 p 的最小编号子节点(g[p][0])
// 若还没记录过,或已有子节点编号更大,则更新为当前 i
if (!g[p][0] || g[p][0] > i)
g[p][0] = i;
}
// 根节点的父亲定义为自己
f[1][0] = 1;
// 若某节点没有子节点,则令它的最小子节点为自己(方便倍增)
for (int i = 1; i <= n; i ++ )
if (!g[i][0])
g[i][0] = i;
// ----------- 倍增预处理 -----------
// f[i][k] = f[f[i][k-1]][k-1]
// g[i][k] = g[g[i][k-1]][k-1]
// 即从 i 连跳 2^(k-1) + 2^(k-1) 步的结果
for (int k = 1; k < M; k ++ )
for (int i = 1; i <= n; i ++ )
{
f[i][k] = f[f[i][k - 1]][k - 1];
g[i][k] = g[g[i][k - 1]][k - 1];
}
// ----------- 处理每个旅行查询 -----------
while (m -- )
{
int s, cnt; // s = 起点编号, cnt = 移动序列长度
cin >> s >> cnt;
// 对每个移动 a_j 按顺序执行
while (cnt -- )
{
int k;
cin >> k;
// 若 k > 0 表示“向上跳 k 次”,则 h 指向 f;
// 若 k < 0 表示“向下跳 |k| 次”,则 h 指向 g。
if (k > 0)
h = f;
else
h = g, k = -k; // 转为正数方便处理
// 倍增跳跃:
// 遍历每一位,如果二进制第 i 位是 1,就跳 2^i 步。
for (int i = 0; i < M; i ++ )
if (k >> i & 1)
s = h[s][i];
}
cout << s << endl; // 输出终点编号
}
return 0;
}
🧠逻辑总结与要点说明
| 操作 | 含义 | 数据结构 | 特殊处理 |
|---|---|---|---|
| 向上跳 | 从当前结点走向父亲 | f[i][k] |
根的父亲设为自己(防止越界) |
| 向下跳 | 从当前结点走到“编号最小的子节点” | g[i][k] |
若无子节点则设为自己 |
| h | 当前操作方向(上或下) | 指向 f 或 g |
用函数指针形式节省代码 |
| 倍增跳 | 用二进制快速跳多步 | O(log n) |
检查每个二进制位 |
⏱️复杂度分析
| 阶段 | 时间复杂度 |
|---|---|
| 建树与预处理 | \(O(n \log n)\) |
| 每次查询 | \(O(k_i \log n)\) ,其中 \(k_i\) 是本次旅行移动数 |
| 总计 | \(O((n + \sum k_i)\log n)\) ,满足题目限制 |

浙公网安备 33010602011771号