0题目原文(本地存了一份)
原题在洛谷上(页头有链接)。别人的网站不归我们管,打不开、改版、题号调整都可能发生。 所以每个解析页都把题面转录一份存在本地,跟着仓库一起进版本库。
转录自洛谷 P4551,日期见页头。两边不一致时信原站。
题目描述
给定一棵 n 个点的带权树,结点下标从 1 开始到 n。求树中所有异或路径的最大值。
异或路径指树上两个结点之间唯一路径上的所有边权的异或值。
输入格式
第一行一个整数 n,表示结点数。
接下来 n − 1 行,给出 u, v, w,分别表示树上的 u 点和 v 点有连边,边的权值是 w。
输出格式
一行,一个整数表示答案。
数据范围
1 ≤ n ≤ 10⁵,0 < u, v ≤ n,0 ≤ w < 2³¹。
时限 1 秒,内存 524288 KB(512 MB)。
输入输出样例
输入
4 1 2 3 2 3 4 2 4 6
输出
7
当两个结点分别是 1、3 时,答案是 7 = 3 XOR 4,取最大值。
1★★★ 这道题一个字符串哈希都用不上 —— 它是这一章的第二个对照组
第 49 章的题单在这道题后面写着:
「⚠ 提高组:它其实是第 50 章 01-Trie 的题。放在这儿是想说明 『把东西变成一个数』之后,路还能往哪儿走。」
⇒ 这一章教的是那半步:把一个复杂的东西压成一个整数,让「比较」变成 O(1)。
★★★ 而这半步在这道题上只有一句话,证明只有一行:
令 d[u] = 根到 u 的所有边权的异或
⇒ u 到 v 的路径异或 = d[u] XOR d[v]为什么:路径 = (根→u) 和 (根→v) 拼起来,两条里根到 LCA 那一段被走了两遍,
而 x XOR x = 0 ⇒ 它自己抵消了。
⇒ 于是题目变成:n 个数里挑两个,异或最大。剩下的就是第 50 章的 01-Trie。
// P4551 最长异或路径 —— 正解:把「树上路径」变成「一个数」,再上 01-Trie。//// ★★ 这道题挂在第 49 章的题单里,可它**一个字符串哈希都用不上** ——// 题单注解自己写着:「它其实是第 50 章 01-Trie 的题。放在这儿是想说明// 『把东西变成一个数』之后,路还能往哪儿走。」// ⇒ 这一章教的是那**半步**:把一个复杂的东西压成一个整数,让「比较」变成 O(1)。// 这道题把同一半步用在**树上的路径**上。//// ★★★ 那半步只有一句话,而且证明只有一行:// 令 d[u] = 根到 u 的所有边权的异或。// 则 u 到 v 的路径异或 = d[u] ^ d[v]。// 为什么:路径 = (根→u) ⊕ (根→v),两条里**根到 LCA 那一段被走了两遍**,// 而 `x ^ x = 0` ⇒ 它自己抵消了。// ⚠⚠ 注意这和[第 52 章树上差分](/ch/52-tree-diff/)那套**求和**的公式**不一样**:// 求和是 `s[u] + s[v] − 2·s[lca]`(要减两遍),异或是**一遍都不用减**。// 照搬那个公式、写成 `d[u] ^ d[v] ^ d[lca]`,就是 p4551Lca.cpp。//// ⇒ 剩下的问题变成:**n 个数里挑两个,异或最大**。01-Trie 从高位往低位贪心,O(31n)。//// ⚠ 两处要数清楚的:// ① 题面写 `0 ≤ w < 2^31` ⇒ 最高位是**第 30 位**(0 基),要跑 30..0 共 **31** 位。// 写成 29..0 就丢最高位(p4551Bit.cpp)。// ② `d[u]` 和答案都 < 2^31 ⇒ `int` **恰好装得下,余量为 0**(和[第 37 章 P3378](/sol/p3378/) 同形)。//// ⚠ 建树用迭代 DFS:顶格 n = 10⁵ 排成一条链,递归**能活但只剩不到两倍余量**(见 p4551Rec.cpp)。#include <bits/stdc++.h>using namespace std;
const int BITS = 31; // 第 30 位到第 0 位
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; if (!(cin >> n)) return 0; vector<int> head(n + 1, -1), nxt(2 * (n > 0 ? n : 1)), to(2 * (n > 0 ? n : 1)), wt(2 * (n > 0 ? n : 1)); int ec = 0; for (int i = 0; i < n - 1; i++) { int u, v, w; cin >> u >> v >> w; to[ec] = v; wt[ec] = w; nxt[ec] = head[u]; head[u] = ec++; to[ec] = u; wt[ec] = w; nxt[ec] = head[v]; head[v] = ec++; }
/* ① 迭代 DFS 求 d[] */ vector<int> d(n + 1, 0); vector<char> vis(n + 1, 0); vector<int> st; st.push_back(1); vis[1] = 1; while (!st.empty()) { int u = st.back(); st.pop_back(); for (int e = head[u]; e != -1; e = nxt[e]) { int v = to[e]; if (vis[v]) continue; vis[v] = 1; d[v] = d[u] ^ wt[e]; st.push_back(v); } }
/* ② 01-Trie:n 个数里挑两个异或最大 */ vector<array<int, 2>> ch(1); ch[0] = { 0, 0 }; for (int i = 1; i <= n; i++) { int cur = 0; for (int b = BITS - 1; b >= 0; b--) { int t = (d[i] >> b) & 1; if (!ch[cur][t]) { ch.push_back({ 0, 0 }); ch[cur][t] = (int)ch.size() - 1; } cur = ch[cur][t]; } } int ans = 0; for (int i = 1; i <= n; i++) { int cur = 0, val = 0; for (int b = BITS - 1; b >= 0; b--) { int t = (d[i] >> b) & 1; if (ch[cur][t ^ 1]) { val |= 1 << b; cur = ch[cur][t ^ 1]; } else cur = ch[cur][t]; } ans = max(ans, val); } cout << ans << '\n'; return 0;}点「运行 ▶」看结果
2★★★ 而这道题最容易挂的地方,是上一章的正确写法
第 52 章树上差分那套求和的公式是所有人都背过的:
路径和 = s[u] + s[v] − 2·s[lca] ← 根到 LCA 那一段被算了两遍,减两遍⇒ 于是「异或版」顺手就写成 d[u] ^ d[v] ^ d[lca] —— 照猫画虎,减一遍。
★★ 可正确答案是一遍都不减:x XOR x = 0,那一段自己就没了。
多异或一个 d[lca],等于把根到 LCA 那一段又加了回去。
⇒ ★★★ 第 52 章那条「上一章的正确写法可能就是这一章的 bug」又一次 —— 而这一次两者之间隔着的不是题目,是运算: 加法要减两遍,异或一遍都不用减。
⚠ 而官方那唯一一组样例挡不住它:样例里最优的那一对是 1 和 3,
它们的 LCA 就是根(d[1] = 0)⇒ 多异或一个 0,什么都没变,照样打出 7。
★ 触发条件:最优的那一对的 LCA 不是根 —— 三档实测被抓 122 / 135 / 201(共 300 轮)。
// P4551 —— ✗ 错法二:照搬[第 52 章树上差分](/ch/52-tree-diff/)那套**求和**的公式。//// ★ 那一章的路径和是:`s[u] + s[v] − 2·s[lca]`(根到 LCA 那一段被算了两遍,要减掉两遍)。// ⚠ 于是「异或版」顺手就写成 `d[u] ^ d[v] ^ d[lca]` —— 照猫画虎,减一遍。//// ★★ 而正确答案是**一遍都不减**:`x ^ x = 0`,那一段自己就抵消了。// ⇒ 多异或一个 `d[lca]`,等于**把根到 LCA 那一段又加了回去**。//// ⇒ ★★★ [第 52 章](/ch/52-tree-diff/)那条「上一章的正确写法可能就是这一章的 bug」又一次 ——// 而这一次两章之间隔着的不是题目,是**运算**:加法要减两遍,异或一遍都不用。//// ⚠ 注意这一版**慢得多**(它要真的求 LCA,而且是 O(n²) 枚举点对),// 所以只在小数据上当错法用 —— 这一页要看的是它**答什么**,不是它跑多快。#include <bits/stdc++.h>using namespace std;
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; if (!(cin >> n)) return 0; vector<vector<pair<int, int>>> g(n + 1); for (int i = 0; i < n - 1; i++) { int u, v, w; cin >> u >> v >> w; g[u].push_back(make_pair(v, w)); g[v].push_back(make_pair(u, w)); } vector<int> d(n + 1, 0), fa(n + 1, 0), dep(n + 1, 0); vector<char> vis(n + 1, 0); vector<int> st(1, 1); vis[1] = 1; while (!st.empty()) { int u = st.back(); st.pop_back(); for (size_t k = 0; k < g[u].size(); k++) { int v = g[u][k].first; if (vis[v]) continue; vis[v] = 1; d[v] = d[u] ^ g[u][k].second; fa[v] = u; dep[v] = dep[u] + 1; st.push_back(v); } } auto lca = [&](int x, int y) { while (dep[x] > dep[y]) x = fa[x]; while (dep[y] > dep[x]) y = fa[y]; while (x != y) { x = fa[x]; y = fa[y]; } return x; }; int ans = 0; for (int i = 1; i <= n; i++) for (int j = i + 1; j <= n; j++) ans = max(ans, d[i] ^ d[j] ^ d[lca(i, j)]); // ⚠ 多异或了一个 cout << ans << '\n'; return 0;}点「运行 ▶」看结果
3⚠ 第二个坑:「w < 2³¹」是 31 位,不是 30 位
题面写的是 0 ≤ w < 2³¹。
⚠ 「小于 2³¹」很容易被读成「30 位」—— 而它其实是 31 位:
最大的 w 是 2³¹ − 1 = 2147483647,二进制是 31 个 1,最高位是第 30 位(0 基)。
⇒ 01-Trie 要跑 b = 30, 29, …, 0,一共 31 层。
★ 触发条件精确到一句话:存在一对 (u, v) 使 d[u] XOR d[v] 的第 30 位是 1。
⚠⚠ 而顺手写的生成器最爱把边权取成 rng() % 1000 这种小数
⇒ 那一档是结构性的精确的 0(三档实测 0 / 300 / 300)。
⇒ 第 46 章 P1100 那条的又一次:顺手写的上界正好卡在 bug 的下面。
w < 2³¹ ⇒ 每个 d[u] < 2³¹,异或不会让它变大 ⇒ 答案 ≤ 2³¹ − 1 = 2147483647
⇒ int 恰好装得下,一个格子都不剩。
⚠ 而这不是纸上谈兵:顶格随机边权跑一遍,答案就是 2147483647 本身
(n = 10⁵ 个 31 位随机数里,两两异或几乎必然凑得出全 1)。
⇒ 和第 35 章 P1886(题面把 int 的 42.9 亿个值全用光)、
第 37 章 P3378(1 ≤ x < 2³¹,余量为 0)是同一个形状:
题面把 int 的值域用满,多一位都不给。
// P4551 —— ✗ 错法一:位数数错了,只做 30 位(第 29 位到第 0 位)。//// ★ 题面写的是 `0 ≤ w < 2^31`。// ⚠ 「小于 2³¹」很容易被读成「30 位」—— 而它其实是 **31 位**(第 30 位到第 0 位,// 最大的 w 是 2³¹ − 1 = 2147483647,二进制是 31 个 1)。//// ★ 触发条件精确到一句话:**存在一对 (u, v) 使 `d[u] ^ d[v]` 的第 30 位是 1**。// ⚠ 而顺手写的生成器最爱把边权取成 `rng() % 1000` 这种小数// ⇒ 那一档是**结构性的精确的 0**([第 46 章 P1100](/sol/p1100/) 那条:// **顺手写的上界正好卡在 bug 的下面**)。#include <bits/stdc++.h>using namespace std;
const int BITS = 30; // ⚠ 少了一位
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; if (!(cin >> n)) return 0; vector<int> head(n + 1, -1), nxt(2 * (n > 0 ? n : 1)), to(2 * (n > 0 ? n : 1)), wt(2 * (n > 0 ? n : 1)); int ec = 0; for (int i = 0; i < n - 1; i++) { int u, v, w; cin >> u >> v >> w; to[ec] = v; wt[ec] = w; nxt[ec] = head[u]; head[u] = ec++; to[ec] = u; wt[ec] = w; nxt[ec] = head[v]; head[v] = ec++; } vector<int> d(n + 1, 0); vector<char> vis(n + 1, 0); vector<int> st(1, 1); vis[1] = 1; while (!st.empty()) { int u = st.back(); st.pop_back(); for (int e = head[u]; e != -1; e = nxt[e]) { int v = to[e]; if (vis[v]) continue; vis[v] = 1; d[v] = d[u] ^ wt[e]; st.push_back(v); } } vector<array<int, 2>> ch(1); ch[0] = { 0, 0 }; for (int i = 1; i <= n; i++) { int cur = 0; for (int b = BITS - 1; b >= 0; b--) { int t = (d[i] >> b) & 1; if (!ch[cur][t]) { ch.push_back({ 0, 0 }); ch[cur][t] = (int)ch.size() - 1; } cur = ch[cur][t]; } } int ans = 0; for (int i = 1; i <= n; i++) { int cur = 0, val = 0; for (int b = BITS - 1; b >= 0; b--) { int t = (d[i] >> b) & 1; if (ch[cur][t ^ 1]) { val |= 1 << b; cur = ch[cur][t ^ 1]; } else cur = ch[cur][t]; } ans = max(ans, val); } cout << ans << '\n'; return 0;}点「运行 ▶」看结果
4★ 两把尺子:n² 和 31n 差 806 倍
// P4551 —— 三条路各做多少次基本操作,以及「int 到底够不够」那笔账。//// ★ 三条路:// ✗ 两两异或 C(n,2) 次 —— 顶格 10⁵ ⇒ **5×10⁹**// ★ 01-Trie 31 n 次插入 + 31 n 次查询 ⇒ 顶格 **6.2×10⁶**// ★ 按位分治 同样 O(31 n),一行代码都不和 Trie 共享(顶格那一档的参照物)//// ★★ 而「要不要 long long」是一句除法就问完的事:// 题面 `0 ≤ w < 2^31` ⇒ 每个 d[u] < 2^31,异或不会变大 ⇒ 答案 ≤ 2³¹ − 1// ⇒ **`int` 恰好装得下,余量为 0**(和[第 37 章 P3378](/sol/p3378/)、// [第 35 章 P1886](/sol/p1886/) 是同一个形状:题面把 int 的值域用满,一个格子都不剩)。// ⚠ 而这份程序顺手把它坐实:顶格随机边权下,**答案就是 2147483647 本身**。//// 用法:./p4551Count [table|csv]#include <bits/stdc++.h>using namespace std;
static string padLeft(const string& s, int width) { int disp = 0; for (unsigned char c : s) { if ((c & 0xC0) == 0x80) continue; disp += (c < 0x80) ? 1 : 2; } return string(max(0, width - disp), ' ') + s;}
static long long ops = 0;
static int trieMax(const vector<int>& d) { vector<array<int, 2>> ch(1); ch[0] = { 0, 0 }; for (size_t i = 0; i < d.size(); i++) { int cur = 0; for (int b = 30; b >= 0; b--) { ops++; int t = (d[i] >> b) & 1; if (!ch[cur][t]) { ch.push_back({ 0, 0 }); ch[cur][t] = (int)ch.size() - 1; } cur = ch[cur][t]; } } int ans = 0; for (size_t i = 0; i < d.size(); i++) { int cur = 0, val = 0; for (int b = 30; b >= 0; b--) { ops++; int t = (d[i] >> b) & 1; if (ch[cur][t ^ 1]) { val |= 1 << b; cur = ch[cur][t ^ 1]; } else cur = ch[cur][t]; } ans = max(ans, val); } return ans;}
static int bruteMax(const vector<int>& d) { int ans = 0; for (size_t i = 0; i < d.size(); i++) for (size_t j = i + 1; j < d.size(); j++) { ops++; ans = max(ans, d[i] ^ d[j]); } return ans;}
int main(int argc, char** argv) { string mode = argc > 1 ? argv[1] : "table"; const int NS[3] = { 1000, 4000, 16000 }; long long tb[3][2]; int ansSame = 1; for (int k = 0; k < 3; k++) { mt19937 rg(20260909u + k); vector<int> d(NS[k]); for (int i = 0; i < NS[k]; i++) d[i] = (int)(rg() & 0x7fffffffu); ops = 0; int a1 = bruteMax(d); tb[k][0] = ops; ops = 0; int a2 = trieMax(d); tb[k][1] = ops; if (a1 != a2) ansSame = 0; } /* 顶格:答案顶到 int 的上限本身 */ mt19937 rg(20260909u); vector<int> big(100000); for (int i = 0; i < 100000; i++) big[i] = (int)(rg() & 0x7fffffffu); int bigAns = trieMax(big);
if (mode == "table") { printf("★ 基本操作次数(n 个数里挑两个异或最大)\n\n"); printf(" %-8s %s %s\n", "n", padLeft("✗ 两两异或", 18).c_str(), padLeft("★ 01-Trie(31 位)", 22).c_str()); for (int k = 0; k < 3; k++) printf(" %-8d %18lld %22lld\n", NS[k], tb[k][0], tb[k][1]); printf(" n 每翻 4 倍:两两异或 ×%.1f、×%.1f(O(n²) 的签名);Trie ×%.1f、×%.1f(线性)\n", (double)tb[1][0] / tb[0][0], (double)tb[2][0] / tb[1][0], (double)tb[1][1] / tb[0][1], (double)tb[2][1] / tb[1][1]); printf(" ⇒ 顶格 n = 10⁵:两两异或 %lld 次,Trie %lld 次 —— 差 %.0f 倍\n\n", 100000LL * 99999 / 2, 62LL * 100000, (100000.0 * 99999 / 2) / (62.0 * 100000)); printf("★ 「int 够不够」:题面 0 ≤ w < 2³¹ ⇒ 答案 ≤ 2³¹ − 1 = 2147483647\n"); printf(" 顶格随机边权实测答案:%d ⇒ %s\n", bigAns, bigAns == 2147483647 ? "★ 正好顶到 int 的上限,余量为 0" : "没顶到"); printf(" 两两异或 ↔ Trie 三档答案一致:%s\n", ansSame ? "是" : "否"); } else { for (int k = 0; k < 3; k++) printf("n%d,%lld %lld\n", NS[k], tb[k][0], tb[k][1]); printf("ansSame,%d\nbigAns,%d\n", ansSame, bigAns); printf("grow,%.1f %.1f %.1f %.1f\n", (double)tb[1][0] / tb[0][0], (double)tb[2][0] / tb[1][0], (double)tb[1][1] / tb[0][1], (double)tb[2][1] / tb[1][1]); } return 0;}点「运行 ▶」看结果
n |
✗ 两两异或 | ★ 01-Trie(31 位) |
|---|---|---|
| 1000 | 499 500 | 62 000 |
| 4000 | 7 998 000 | 248 000 |
| 16000 | 127 992 000 | 992 000 |
n 每翻 4 倍 |
×16.0(O(n²) 的签名) |
×4.0(线性) |
⇒ 顶格 n = 10⁵:两两异或 4 999 950 000 次,Trie 6 200 000 次 —— 差 806 倍。
★ 而顶格的秒表:正解 0.06 秒 / 时限 1 秒(链形和随机树一样),余量 16 倍。
⚠ 这道题一个部分分档都没有 ⇒ O(n²) 那条路一分不给
(和第 48 章 P4391 一样)。
顶格 n = 10⁵ 上两两异或要 5×10⁹ 对,跑不完 ⇒ 拿暴力当参照物这条路断了。
★ 而按位分治是另一条完全不同的路(从高位往下把当前这堆数按「这一位是 0 还是 1」
分成两半,两半都非空 ⇒ 这一位一定能取到 1),同样 O(31n),
和 01-Trie 一行代码都不共享。
⇒ 第 19 章 P1803 立的那条规矩,换一道题又用上一次。
5★★ 递归会不会爆栈:顶格活着,可余量只有 1.3 倍
第 29 章 P5318 立过的规矩:「递归会不会爆栈」不是写法的属性,
是「栈上限 ÷ 每层字节数」这道除法 —— 同一台机器上,
函数体里多一句 string 拼接,每层就从 48 字节涨到 341 字节、能递归的层数从 17 万掉到 2.4 万。
⇒ 所以只能量。把这道题的 DFS 写成递归(p4551Rec.cpp),在链上一档档往上试
(A 机 · WSL2 · ulimit -s = 8192 KB · 2026-09-09):
| 链的长度 | 递归版 |
|---|---|
n = 10⁵(题面顶格) |
★ 活着,答案正确 |
n = 1.3×10⁵ |
活着 |
n = 1.31×10⁵ |
✗ 段错误(退出码 139) |
n = 2×10⁵ |
✗ 段错误 |
⇒ 门槛在 13.0 万 ~ 13.1 万层之间,每层约 64 字节(8192 KB ÷ 13 万)。 ★ 顶格 10 万层能活,但余量只有 1.30 倍。
⇒ ★★ 结论还是那句:顶格题一律写迭代,别赌 —— 赌赢了也只赢一点点。 (对照:第 29 章 P5318 那份精简递归是 48 字节 / 17.4 万层, 第 30 章 B3625 独立量到同一个数。这一份多了三个数组下标, 每层从 48 涨到 64 字节,门槛就从 17.4 万掉到 13.0 万。)
6★ 对拍:三档 × 300 轮
// P4551 数据生成器(对拍用)。用法:./p4551Gen <seed> [档位] [n],不给档位就是**最终档 2**。//// ★ 每个错法靠什么现形:// ①只做 30 位 ← 要「**某一对的异或第 30 位是 1**」// ⚠ 而顺手写的边权是 `rng() % 1000` 这种小数 ⇒ 那一档是**结构性的 0**// ②照搬树上差分 ← 只要「最优的那一对的 LCA 不是根」,随机就抓//// 档位:// 0 ★ 顺手写法:随机树,边权 0~999 —— ① 在这儿一次都碰不到// 1 ★ 边权照题面顶格(0 ≤ w < 2^31)—— ① 这才有得谈// 2 ★★ 最终档 = 1 + 一半轮次造**链**(LCA 一定不是根,而且顺带压递归深度)// 3 ★ 顶格链(配合 `n`):给「递归会不会爆栈」和顶格秒表用#include <bits/stdc++.h>using namespace std;
static mt19937 rng;static unsigned ru(unsigned m) { return rng() % m; }
int main(int argc, char** argv) { unsigned seed = argc > 1 ? (unsigned)atoi(argv[1]) : 1u; int level = argc > 2 ? atoi(argv[2]) : 2; int n = argc > 3 ? atoi(argv[3]) : 0; rng.seed(seed * 2654435761u + 555u); if (n <= 0) n = (level == 3) ? 100000 : 6 + (int)ru(15);
bool chain = (level == 3) || (level == 2 && (ru(2) == 0)); printf("%d\n", n); for (int v = 2; v <= n; v++) { int u = chain ? v - 1 : 1 + (int)ru((unsigned)(v - 1)); unsigned w; if (level == 0) w = ru(1000); else { w = rng(); w &= 0x7fffffffu; } // 0 ≤ w < 2^31 printf("%d %d %u\n", u, v, w); } return 0;}点「运行 ▶」看结果
(参照物是两两异或;正解 vs 按位分治:900 轮 0 组不一致)
| 档位 | ✗ 只做 30 位 | ✗ 照搬「减 LCA」 |
|---|---|---|
0 ★ 顺手写法:随机树,边权 0~999 |
★ 0 | 122 |
1 ★ 边权照题面顶格(0 ≤ w < 2³¹) |
300 | 135 |
| 2 ★★ 最终档 = 1 + 一半轮次造链 | 300 | 201 |
-
★★★ 「只做 30 位」在顺手那一档是结构性的精确的 0 —— 边权只有 0~999(10 位)⇒ 第 30 位永远是 0 ⇒ 那个 bug 一次都碰不到。 ⚠ 而它的触发条件 ≡ 抓获数:「答案 ≥ 2³⁰」的轮数和被抓的轮数一格不差 (0 / 300 / 300)。 ⇒ ★ 救法只有一个:照抄题面那行数据范围(
0 ≤ w < 2³¹),别自己缩小。 -
★ 「减 LCA」那一列随档位单调上升 122 → 135 → 201 —— 旋钮是「最优的那一对的 LCA 离根有多远」, 而造链是把它推上去最直接的办法(链上任意两点的 LCA 就是编号小的那个)。 ⚠ 顺带:造链这一档同时把递归深度也压满了 —— 一个档位干了两件事。
7★ 哪一版就已经能过了
| 版本 | 结果 | 说明 |
|---|---|---|
★ p4551.cpp(01-Trie + 迭代 DFS) |
AC | 顶格 0.06 秒,余量 16 倍 |
★ p4551Div.cpp(按位分治) |
AC | 同样 O(31n),顶格 0.07 秒 |
★ p4551Rec.cpp(递归 DFS) |
⚠ 能过,余量 1.3 倍 | 顶格链 10 万层,门槛 13.0 万 |
| ✗ 两两异或 | TLE | 5×10⁹ 对,而这道题一个部分分档都没有 |
| ✗ 只做 30 位 | WA | ⚠ 顺手写的对拍抓不到(结构性的 0) |
| ✗ 照搬「减 LCA」 | WA | ⚠ 官方样例放过(最优那一对的 LCA 正好是根) |
⇒ ★★★ 一句话带走:这一章教会你「把东西压成一个数」, 可压完之后要用什么工具,是另一件事。 串压成数 ⇒ 排序 / 哈希表(P3370); 路径压成数 ⇒ 按位的数据结构(01-Trie / 按位分治)。 ⇒ 而这两件事之间没有任何公共代码 —— 共享的只是那句「变成一个数」。