题单 · 习题解析

洛谷 P5903 【模板】树上 K 级祖先

★★★ 题目背景第一行就把话挑明了(「本题仅作为长链剖分求树上 k 级祖先评测用」),所以这一页不假装能教长链剖分,它量的是**倍增在这道题上还剩多少余量** —— 而答案是「**看树长什么样**」:顶格 `n = 5×10⁵`、`q = 5×10⁶`,随机树 **0.53 秒**、完全二叉树 0.62 秒、**链 5.45 秒(时限 3 秒,超 1.8 倍)**;★★★ 而最反直觉的一条是**随机树上「一步一步爬」比倍增还快 1.3 倍**(0.40 vs 0.53 秒,七轮交替取中位数)—— 而倍增做的活**少 3.7 倍**(880 万次跳 vs 3285 万步)⇒ 又一次[两把尺子打架](/sol/p1074/),原因很具体:暴力那几步是在 **2 MB** 的 `fa[]` 里追指针,倍增是往 **40 MB** 的表里随机摸;⚠ 而链上两把尺子的差距从 3.7 倍变成 **14 543 倍**(暴力 6.25×10¹¹ 步)⇒ **主语从头到尾只有一个:这棵树的深度**(`k` 是**对深度取模**摇出来的);★★ 于是它和隔壁 [P3379](/sol/p3379/) 正好是一对:**同一条尺子(树的形状)连着两道题决定生死,而两次死的是不同的算法**;★★★ 第三条:**那张表「换个下标顺序」的赢家在这两道题上是相反的** —— P3379 上 `up[MAXN][LOG]` 赢 10.66 倍,这道题上它反而输 2.0 倍 ⇒ **主语是「查询会读到哪几层」**(LCA 要把 20 层全比一遍 ⇒ 40 MB 全碰;k 级祖先只读 `k` 的那几位,随机树上 `k ≤ 31` ⇒ 只碰最低 5 层 = 10 MB),★ 而链上两版打平(0.96)正好是这条解释的自检;★★★ 四个坑一个都不在「怎么跳」上:随机函数**必须一字不差**(`ui` 是 unsigned)/**强制在线**(`xor ansᵢ₋₁`,不能离线排序)/**根的深度是 1** —— ★★ 而这是[第 52 章](/ch/52-tree-diff/)那条「上一章的正确写法就是这一章的 bug」最直白的一次(正文写的正是 `dep[root] = 0`,而这里 `k = r % d[x]` ⇒ **除以零,当场崩**)/输出要 `long long`;⚠⚠ 而「累加用 int」那一列**四档全是精确的 0,且这个 0 能算**:越界要 `i ≥ 2³¹/n`,对拍的 `n ≤ 30` ⇒ 要 `i ≥ 7158 万`而 `q ≤ 60` ⇒ **加轮数没用、加 q 也没用,得同时放大 n 和 q**

原题:洛谷 P5903出自 第 51 章 LCA 与倍增:把「往上跳多少步」拆成二进制 的题单题面本地存档:2026-09-11
⚠ 先自己写一遍,再往下看

这一页不是标准答案,是一条阶梯:把大多数人真实会写出的第一版摆出来, 看它错在哪、慢在哪,再一步一步改。所以它对「已经自己动手撞过一次墙」的人最有用。
还没写过的话,先去写 —— 写出来的哪怕是错的,也比直接看这一页收获大。

0题目原文(本地存了一份)

为什么要在这儿抄一份题面

原题在洛谷上(页头有链接)。别人的网站不归我们管,打不开、改版、题号调整都可能发生。 所以每个解析页都把题面转录一份存在本地,跟着仓库一起进版本库。

转录自洛谷 P5903,日期见页头。两边不一致时信原站。

题目背景

本题仅作为长链剖分求树上 k 级祖先评测用,不保证卡掉了其他复杂度不正确的做法。

题目描述

给定一棵 n 个点的有根树。

q 次询问,第 i 次询问给定 xᵢ, kᵢ,要求点 xᵢkᵢ 级祖先,答案为 ansᵢ。 特别地,ans₀ = 0

本题中的询问将在程序内生成。给定一个随机种子 s 和一个随机函数 get(x)

#define ui unsigned int
ui s;

inline ui get(ui x) {
	x ^= x << 13;
	x ^= x >> 17;
	x ^= x << 5;
	return s = x; 
}

你需要按顺序依次生成询问。设 dᵢ 为点 i 的深度,其中根的深度为 1

对于第 i 次询问,xᵢ = ((get(s) xor ansᵢ₋₁) mod n) + 1kᵢ = (get(s) xor ansᵢ₋₁) mod d_{xᵢ}

输入格式

第一行三个整数 n, q, s

第二行 n 个整数 f₁…fₙ,其中 fᵢ 表示 i 的父亲。特别地,若 fᵢ = 0,则 i 为根。

输出格式

一行一个整数,表示 xori = 1qi × ansᵢ

数据范围

  • 对于 20% 的数据,n, q ≤ 10³
  • 对于 50% 的数据,n, q ≤ 10⁵
  • 对于 100% 的数据,2 ≤ n ≤ 5×10⁵1 ≤ q ≤ 5×10⁶1 ≤ s < 2³²

时限 3 秒,内存 512000 KB(500 MiB)。

输入输出样例

输入

6 3 7
5 5 2 2 0 3

输出

1

x₁ = 4k₁ = 1ans₁ = 2x₂ = 6k₂ = 3ans₂ = 5x₃ = 3k₃ = 0ans₃ = 3。 ⇒ 1×2 xor 2×5 xor 3×3 = 2 xor 10 xor 9 = 1

★ 注意第三问:k₃ = 0 —— 0 级祖先就是它自己

1★★★ 题面第一行就把话挑明了:这道题的正解不是倍增

★★ 先说清楚这一页在干什么

「本题仅作为长链剖分求树上 k 级祖先评测用,不保证卡掉了其他复杂度不正确的做法。」

出题人要收的是长链剖分:预处理 O(n log n)每次询问 O(1)。 而倍增是每次询问 O(log n) —— q 到 5×10⁶ 时它慢一个 log。

⇒ 所以这一页不假装能教你写长链剖分(那超出了本书的范围),它做的是另一件更有用的事: 把「倍增在这道题上还剩多少余量」量出来 —— 而量完之后的答案很有意思: 看树长什么样(第 ③ 步)。

★ 而这道题真正会咬人的四处,一处都不在「怎么跳」上:

题面写在哪儿 后果
⚠⚠ 随机函数必须一字不差uiunsigned 题面把代码原样给了你 换成 int ⇒ 在算另一串询问
⚠⚠ 强制在线xor ansᵢ₋₁ 那两个公式里 不能离线、不能排序、不能分组
根的深度是 1 「其中根的深度为 1」 ★★ 写成 0 ⇒ mod d[x] 是一次除以零
⚠ 输出要 long long i × ansᵢ 最大 5×10⁶ × 5×10⁵ = 2.5×10¹²
p5903.cpp★ 倍增版:O(q log n)
// P5903【模板】树上 K 级祖先 —— 用倍增写(★ 而题面自己说了:这道题的正解不是它)
//
// ⚠⚠ 题目背景第一行就把话挑明了:
// 「**本题仅作为长链剖分求树上 k 级祖先评测用,不保证卡掉了其他复杂度不正确的做法。**」
// ⇒ 出题人想收的是 **O(1) 查询**的长链剖分(预处理 O(n log n)、每次询问 O(1))。
// 倍增是 **O(log n) 查询** ⇒ `q` 到 5×10⁶ 时它慢一个 log。
// ⇒ 这一页要回答的就是那句话的分量:**倍增在这道题上到底还剩多少余量**(解析页第 ③ 步)。
//
// ★ 而这道题真正会咬人的四处,一处都不在「怎么跳」上:
// ① ⚠⚠ **询问是程序内生成的,而且强制在线** —— `x_i` 和 `k_i` 都要异或上 `ans_{i−1}`
// ⇒ **不能离线排序**,也不能先把 q 个询问读进来。
// ② ⚠⚠ **那个随机函数必须一字不差地照抄**:`ui` 是 **unsigned**,
// 三次移位是 `<<13 / >>17 / <<5`,而且 `get` 会**把 s 改掉**(`return s = x`)。
// ③ ⚠ **根的深度是 1**(题面写死的)—— 因为 `k_i` 是对 `d[x_i]` 取模,
// 根的深度要是写成 0,那儿就是一次**除以零**。
// ④ ⚠⚠ **输出要 `long long`**:`i × ans_i` 最大 5×10⁶ × 5×10⁵ = **2.5×10¹²**。
//
// ⚠⚠ 存法**不能**照搬 [P3379](/sol/p3379/) 那一页量出来的结论(解析页第 ③ 步量了):
// 那道题求 LCA 要**把 20 层全比一遍** ⇒ `up[MAXN][LOG]`(同一个点的 20 层挨在一起)赢 10 倍;
// 而这道题只读 `k` 的那几个二进制位 —— 随机树上 `k ≤ 31` ⇒ **只碰得到最低 5 层**
// ⇒ 反过来是 `up[LOG][MAXN]`(同一层的点挨在一起,只碰 10 MB)赢,端到端 0.77 vs 1.17 秒。
// ⇒ ★★ 「换个存法快多少」的主语不是这张表,是**你会去读它的哪一部分**。
#include <bits/stdc++.h>
using namespace std;
typedef unsigned int ui;
const int MAXN = 500005;
const int LOG = 20;
int up[LOG][MAXN];
int dep[MAXN]; // ⚠ ③ 根的深度是 1
int fa[MAXN];
int n, root;
long long q;
ui s;
/** ⚠ ② 题面给的随机函数,一个字都不能改 */
inline ui getRnd(ui x) {
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
return s = x;
}
/* ---- 快读:输入只有 n + 3 个整数,可那也有 3.4 MB ---- */
static char ibuf[1 << 22];
static size_t ilen = 0, ipos = 0;
inline int gc() {
if (ipos == ilen) { ilen = fread(ibuf, 1, sizeof(ibuf), stdin); ipos = 0; if (!ilen) return -1; }
return ibuf[ipos++];
}
inline long long readInt() {
int c = gc();
long long x = 0;
while (c < '0' || c > '9') c = gc();
while (c >= '0' && c <= '9') { x = x * 10 + (c - '0'); c = gc(); }
return x;
}
int main() {
n = (int)readInt();
q = readInt();
s = (ui)readInt();
vector<vector<int> > son(n + 1);
for (int i = 1; i <= n; i++) {
fa[i] = (int)readInt();
if (fa[i] == 0) root = i;
else son[fa[i]].push_back(i);
}
/* BFS 求 dep 和 up[·][0](⚠ 父亲编号不保证更小,所以不能顺着 1..n 递推)*/
{
vector<int> que;
que.reserve(n);
dep[root] = 1; // ⚠ ③ 根的深度是 1
up[0][root] = root; // 根的父亲设成它自己,跳过头就停在根
que.push_back(root);
for (size_t i = 0; i < que.size(); i++) {
int u = que[i];
for (size_t j = 0; j < son[u].size(); j++) {
int v = son[u][j];
dep[v] = dep[u] + 1;
up[0][v] = u;
que.push_back(v);
}
}
}
for (int k = 1; k < LOG; k++)
for (int v = 1; v <= n; v++)
up[k][v] = up[k - 1][up[k - 1][v]];
long long ans = 0; // ⚠ ④ i × ans_i 最大 2.5×10¹²
ui last = 0; // ans_0 = 0
for (long long i = 1; i <= q; i++) {
int x = (int)(((getRnd(s) ^ last) % (ui)n) + 1);
ui k = (getRnd(s) ^ last) % (ui)dep[x];
int u = x;
for (int b = 0; b < LOG; b++) // 把 k 拆成二进制,该跳哪几位就跳哪几位
if ((k >> b) & 1u) u = up[b][u];
last = (ui)u;
ans ^= i * (long long)u;
}
printf("%lld\n", ans);
return 0;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果

2第一版:一步一步往上爬 k 次

p5903Brute.cpp✗ 第一版(也是对拍的标准答案)
★ 它当标准答案最合适 —— 因为这道题的坑全在「怎么摇询问」上

这道题的四个坑(随机函数 / 强制在线 / 深度从 1 / long long两版都得对, 而「怎么找 k 级祖先」这一步暴力压根没得错。 ⇒ 于是对拍抓到的每一次不一致,都精确地指向那四个坑之一。

3★★★ 这道题最反直觉的一条:随机树上,「一步一步爬」比倍增还快

p5903Count.cpp★ 数步数(机器无关)+ 三条路各跑一遍
// P5903 的两笔账:两条路各做多少次基本动作(机器无关),以及那张表的两种存法差多少
//
// 用法:./p5903Count <csv|table> [q] < 一份输入(q 不给就用输入里的)
//
// ★ 这道题的 `k_i` 是**对深度取模**摇出来的 ⇒ 平均要跳的步数直接由树的形状决定:
// 随机树深度只有 40 出头 ⇒ 平均爬 20 步;一条链就是平均爬 n/4 = 12.5 万步。
// ⇒ 「一步一步爬」和「倍增」谁快,在这道题上**由数据说了算**。
#include <bits/stdc++.h>
#include <sys/time.h>
using namespace std;
typedef unsigned int ui;
static double nowMs() { struct timeval tv; gettimeofday(&tv, nullptr); return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0; }
const int MAXN = 500005;
const int LOG = 20;
static int upA[LOG][MAXN]; // 正文那种存法
static int upB[MAXN][LOG]; // 转置([P3379](/sol/p3379/) 那一页量出来更快的那种)
static int dep[MAXN], fa[MAXN];
static int n, root;
static long long q;
static ui s;
static inline ui getRnd(ui x) { x ^= x << 13; x ^= x >> 17; x ^= x << 5; return s = x; }
int main(int argc, char** argv) {
string mode = argc > 1 ? argv[1] : "table";
if (scanf("%d %lld %u", &n, &q, &s) != 3) return 0;
if (argc > 2) q = atoll(argv[2]);
ui s0 = s;
vector<vector<int> > son(n + 1);
for (int i = 1; i <= n; i++) {
if (scanf("%d", &fa[i]) != 1) return 0;
if (fa[i] == 0) root = i; else son[fa[i]].push_back(i);
}
{
vector<int> que; que.reserve(n);
dep[root] = 1; fa[root] = root; upA[0][root] = root; que.push_back(root);
for (size_t i = 0; i < que.size(); i++) {
int u = que[i];
for (size_t j = 0; j < son[u].size(); j++) {
int v = son[u][j];
dep[v] = dep[u] + 1; upA[0][v] = u; que.push_back(v);
}
}
}
for (int k = 1; k < LOG; k++) for (int v = 1; v <= n; v++) upA[k][v] = upA[k - 1][upA[k - 1][v]];
for (int v = 1; v <= n; v++) for (int k = 0; k < LOG; k++) upB[v][k] = upA[k][v];
int maxDep = 0;
for (int i = 1; i <= n; i++) maxDep = max(maxDep, dep[i]);
/* ① 机器无关:暴力要爬多少步、倍增要跳多少次(先把询问摇一遍,顺手数出来) */
long long climb = 0, jumps = 0;
{
s = s0;
ui last = 0;
for (long long i = 1; i <= q; i++) {
int x = (int)(((getRnd(s) ^ last) % (ui)n) + 1);
ui k = (getRnd(s) ^ last) % (ui)dep[x];
climb += k;
jumps += __builtin_popcount(k);
int u = x;
for (int b = 0; b < LOG; b++) if ((k >> b) & 1u) u = upB[u][b];
last = (ui)u;
}
}
/* ② 三条路各跑一遍,只量查询那一段 */
long long ansA = 0, ansB = 0, ansC = 0;
double msA, msB, msC;
{
s = s0; ui last = 0; double t = nowMs();
for (long long i = 1; i <= q; i++) {
int x = (int)(((getRnd(s) ^ last) % (ui)n) + 1);
ui k = (getRnd(s) ^ last) % (ui)dep[x];
int u = x;
for (int b = 0; b < LOG; b++) if ((k >> b) & 1u) u = upA[b][u];
last = (ui)u; ansA ^= i * (long long)u;
}
msA = nowMs() - t;
}
{
s = s0; ui last = 0; double t = nowMs();
for (long long i = 1; i <= q; i++) {
int x = (int)(((getRnd(s) ^ last) % (ui)n) + 1);
ui k = (getRnd(s) ^ last) % (ui)dep[x];
int u = x;
for (int b = 0; b < LOG; b++) if ((k >> b) & 1u) u = upB[u][b];
last = (ui)u; ansB ^= i * (long long)u;
}
msB = nowMs() - t;
}
if (climb <= 2000000000LL) { // 爬得动才量,链上就别指望了
s = s0; ui last = 0; double t = nowMs();
for (long long i = 1; i <= q; i++) {
int x = (int)(((getRnd(s) ^ last) % (ui)n) + 1);
ui k = (getRnd(s) ^ last) % (ui)dep[x];
int u = x;
for (ui c = 0; c < k; c++) u = fa[u];
last = (ui)u; ansC ^= i * (long long)u;
}
msC = nowMs() - t;
} else { msC = -1; ansC = ansA; }
char buf[64];
vector<pair<string, string> > out;
auto putD = [&](const string& k, double x, int p = 1) { snprintf(buf, sizeof(buf), "%.*f", p, x); out.push_back(make_pair(k, string(buf))); };
out.push_back(make_pair("n", to_string(n)));
out.push_back(make_pair("q", to_string(q)));
out.push_back(make_pair("maxdep", to_string(maxDep)));
out.push_back(make_pair("climb", to_string(climb)));
out.push_back(make_pair("jumps", to_string(jumps)));
out.push_back(make_pair("same", string((ansA == ansB && ansA == ansC) ? "1" : "0")));
putD("ms_A", msA);
putD("ms_B", msB);
putD("ms_climb", msC);
putD("ratio_AB", msB > 0 ? msA / msB : 0, 2);
if (mode == "csv") for (size_t i = 0; i < out.size(); i++) printf("%s,%s\n", out[i].first.c_str(), out[i].second.c_str());
else for (size_t i = 0; i < out.size(); i++) printf(" %-10s %s\n", out[i].first.c_str(), out[i].second.c_str());
return 0;
}
点一下即可编辑
输出
点「运行 ▶」看结果
p5903GenBig.cpp★ 顶格生成器:rand / chain / binary
★★★ 顶格 n = 5×10⁵、q = 5×10⁶(本机实测,端到端 7 轮交替取中位数)
树的形状 最大深度 ✗ 暴力爬多少步 ★ 倍增跳多少次 ✗ 暴力 ★ 倍增
rand 32 32 850 000 8 827 429 0.40 秒 0.53 秒
binary 19 42 366 214 9 692 713 0.48 秒 0.62 秒
chain 500 000 625 126 801 694 42 984 954 跑不完 5.45 秒

⇒ ★★★ 随机树上:倍增做的活少 3.7 倍,秒表却慢 1.3 倍。 又一次两把尺子打架 —— 而这次原因很具体: 暴力那 6.57 步是在一个 2 MBfa[] 里追指针, 倍增那 1.77 次跳是往一张 40 MB 的表里随机摸,外加每次询问都要把 20 位扫一遍

⚠⚠ 而 chain 那一行把它整个翻过来:两把尺子的差距从 3.7 倍变成 14 543 倍, 暴力那 6.25×10¹¹ 步彻底跑不完。 ⇒ ★★ 主语从头到尾只有一个:这棵树的深度。kᵢ对深度取模摇出来的 ⇒ 平均要爬深度的一半。)

★★★ 端到端:倍增在链上 TLE,在随机树上轻松过(时限 3 秒)
顶格形状 ★ 倍增(端到端) 余量
rand 0.53 秒 5.7 倍
binary 0.62 秒 4.8 倍
chain 5.45 秒 超时 1.8 倍

⇒ 这就是题目背景那句话的分量:倍增不是「一定过不去」,是「链上过不去」。 ★ 而洛谷这道题的数据到底有没有链,题面没说(「不保证卡掉了其他复杂度不正确的做法」)—— 这一页量的是题面顶格的最坏形状,那才是能写下来的结论。

★★★ 而这道题和隔壁 [P3379](/sol/p3379/) 正好是一对
随机树上死的是 上死的是
P3379(问 LCA) —— 暴力(爬 833 亿步)
这道题(问 k 级祖先) —— 倍增(5.45 秒 / 时限 3 秒)

⇒ ★★ 同一条尺子(树的形状)连着两道题决定生死,而两次死的是不同的算法 —— [P3379] 上暴力在随机树里比倍增还快 7.5 倍,这道题上暴力在随机树里比倍增还快 1.3 倍, 两次都是那句「随机树的深度只有 log 级」在起作用第 51 章第 3 步)。

★★★ 外加一条:那张表「换个下标顺序」的赢家,在这两道题上是相反的

P3379 那一页量出:把 up[LOG][MAXN] 换成 up[MAXN][LOG], 顶格星形树上查询快 10.66 倍同一个动作搬到这道题上,反而变慢一半:

顶格形状 最大深度 k 最多几个二进制位 A = up[LOG][MAXN](★ 正解用的) B = up[MAXN][LOG] A÷B
rand 32 5 0.53 秒 1.06 秒 0.50
binary 19 5 0.62 秒 1.17 秒 0.53
chain 500 000 19 5.45 秒 5.65 秒 0.96

(端到端、七轮交替取中位数。⚠ 这一格必须这么量:这台机器上「往 40 MB 的表里随机摸」 单跑一次能从 0.53 秒飘到 1.39 秒,而七轮交替里 A 轮轮都赢 —— 结论只敢用「谁赢」这一层。)

⇒ ★★★ 主语是「查询会读到哪几层」

  • [P3379] 求 LCA 时第 ② 段要把 20 层全比一遍 ⇒ 40 MB 全都要碰 ⇒ B(同一个点的 20 层挨在一起)赢;
  • 这道题只读 k 的那几个二进制位,而随机树的 k ≤ 31只碰得到最低 5 层 ⇒ A(同一层的点挨在一起)只碰 10 MB,反而赢。

★ 而 chain 那一行是这条解释的自检:深度到 5×10⁵ ⇒ 20 层全碰得到 ⇒ 两版打平(0.96)。 ⇒ ★★ 「换个存法快多少」这句话,主语不是这张表,是「你会去读它的哪一部分」。

4⚠ 四个坑,而每一个都写在题面上

p5903Int.cpp✗ 错法①:随机函数用了 int(题面写的是 unsigned int)
p5903Dep0.cpp✗ 错法②:根的深度写成 0
p5903Off.cpp✗ 错法③:忘了异或 ansᵢ₋₁(把强制在线当成普通随机)
p5903Int32.cpp✗ 错法④:累加输出用 int
★★★ 「根的深度是 0 还是 1」,在这道题上不是差一格,是除以零

第 51 章第 7 步那份 fast.cpp 里写的是 dep[root] = 0 —— 那是对的, 因为那道题只拿 dep 去算深度差。 而这道题的题面写死了「根的深度为 1」,因为 kᵢ 是对 d[xᵢ] 取模摇出来的

   dep[root] = 0   ⇒   k = r % 0   ⇒   SIGFPE,程序直接崩

⇒ ★★★ 这是第 52 章那条「上一章的正确写法就是这一章的 bug」 最直白的一次 —— 而且这次连「错得悄无声息」都不是,它是当场崩

5★ 对拍:四个错法 + 一份「一律输出 0」

p5903Zero.cpp★ 试金石:一律输出 0
p5903Gen.cpp★ 生成器:四个档位
★★★ 300 轮 × 四档(本机实测,2026-09-11)
档位 ①Int ②Dep0 ③Off ④Int32 试金石
0 顺手写法(随机小树、q = 5~30 299 299 300 0 299
1 链 299 300 299 0 300
2 星形 300 300 298 0 299
3 最终档(形状随机 + q 大一些) 300 300 300 0 300

★★★ 前三列几乎满格 —— 因为它们都在算另一串询问,一步走岔就再也回不来。 ⇒ ★ 这也说明这道题的对拍很便宜:随手造一棵小树就能把三个坑全抓住。

⚠⚠ 而 ④Int32 那一列是四个精确的 0,并且这个 0 是能算出来的i × ansᵢ 要越过 2³¹ 需要 i ≥ 2³¹ / n,而对拍的 n ≤ 30 ⇒ 得 i ≥ 7.2 × 10⁷ —— 而这一档的 q 最多 60

那条线在哪儿
对拍(n ≤ 30q ≤ 60 i ≥ 71 582 789结构上永远够不着
题面顶格(n = 5×10⁵ i ≥ 4295q 到四千多就开始错

⇒ ★★ 加轮数没用,加 q 也没用 —— 得同时把 nq 都放大。 顶格 rand 上实测:正解 1418135787338int796579658

6★ 哪一版就已经能过了

★★ 结论:倍增稳拿 50 分,而满不满分要看数据里有没有链
数据档 ✗ 一步一步爬 ★ 倍增
20%(n, q ≤ 10³ ✓ 秒过
50%(n, q ≤ 10⁵ ⚠ 随机树 0.01 秒,链上 8.75 秒 0.04 秒
100%(n ≤ 5×10⁵q ≤ 5×10⁶ ✗ 链上外推约 2200 秒 ⚠ 随机树 0.53 秒,链上 5.45 秒

⇒ ★★ 暴力稳拿 20 分n, q ≤ 10³ 时最坏也只爬 5×10⁵ 步); 倍增稳拿 50 分,满分则取决于「那 10 个测试点里有没有深树」。

★ 而题目背景那句话已经把答案写了一半:出题人没打算卡别的做法, 他只是把 q 开到 5×10⁶,让 O(1)O(log n) 之间那个 log 显出来。

★ 一句话带走

这道题第一次把「输入很小、工作量巨大」摆到台面上 —— 输入只有 n + 3 个整数(3.4 MB),输出只有一个数, 而中间要在线地做 5×10⁶ 次询问。 ⇒ 于是这一页的每一个坑(随机函数、强制在线、深度从 1、long long都长在「怎么把询问摇出来」上,一个都不在「怎么找祖先」上