1 条题解
-
0
火星巡演最大好评问题题解
一、题目核心拆解
本题是树结构路径查询 + 异或运算优化的综合问题,需分4步解决:
- 路径定位:找到城市 到 的路径(树结构,依赖LCA分解路径);
- 最大异或节点筛选:在路径中找到使 最大的城市 (需高效路径异或最大值查询);
- 异或和计算:计算路径中除 外所有城市 的异或和 (利用异或前缀和性质);
- 区间最大异或:在 中找到使 最大的值(经典区间异或最大值问题)。
关键约束:,需所有步骤均为 或 复杂度( 为 最大值,约 ,对应30位二进制)。
二、核心算法与预处理
1. 树结构基础:LCA与异或前缀和
(1)LCA(最近公共祖先)
- 作用:将 到 的路径分解为 ,避免暴力遍历路径超时。
- 实现:倍增法,预处理时间 ,单次查询 。
- 定义 :节点 的 级祖先;
- 定义 :节点 到根的深度。
(2)异或前缀和
- 定义: 表示根节点到 的路径上所有 的异或和。
- 路径异或和公式: 到 的路径总异或和为
$\text{total\_xor} = xor\_sum[u] \oplus xor\_sum[v] \oplus x_i[\text{LCA}(u,v)]$
(原因: 含 , 含 , 被异或两次抵消,需补回一次)。
2. 路径最大异或查询:可持久化字典树
(1)问题需求
需快速查询 到 路径上,与给定 异或值最大的 (即 )。
(2)可持久化字典树原理
- 核心思想:每个节点 对应一棵字典树,存储从根到 路径上所有 的30位二进制(高位到低位)。
- 继承关系:节点 的字典树基于其父节点的字典树构建,仅新增 的二进制路径,保证空间效率 。
(3)查询逻辑
到 的路径可拆分为 、、,查询时:
- 用 的字典树、 的字典树、 父节点的字典树,合并得到路径上所有 的字典树;
- 按二进制高位到低位遍历字典树,优先选择与 当前位不同的分支,最大化 ,同时记录对应 (记为 , 为目标节点)。
3. 区间最大异或:带约束的01字典树
(1)问题需求
给定 和区间 ,求 ()。
(2)算法设计
无需插入 所有数,而是在查询时结合 的二进制约束,贪心选择最优位:
- 从最高位(30位)到最低位遍历;
- 对当前位 ( 的当前位),优先尝试选择 (最大化异或),若选择后剩余位组成的数超过 的对应部分,则只能选择 ;
- 最终得到的路径对应最大的 。
三、完整解题流程
步骤1:预处理
- 建树与LCA倍增预处理:
- 输入树的边,构建邻接表;
- 预处理 表( 取0到20,因 )和 数组。
- 计算异或前缀和 :
- 从根节点出发BFS/DFS,计算每个节点的 。
- 构建可持久化字典树:
- 从根节点出发,每个节点 的字典树继承父节点,插入 的30位二进制。
步骤2:处理单次查询()
- 求LCA:计算 。
- 查最大异或节点:用可持久化字典树查询 到 路径上 ,得到对应 ( 为跳过的节点)。
- 计算 :
$\text{total\_xor} = xor\_sum[u] \oplus xor\_sum[v] \oplus x_i[lca]$
(异或中去掉 等价于再异或一次 )。 - 求最大 :用带约束的01字典树,查询 中与 异或最大的值,即为答案。
四、关键代码实现(C++)
1. LCA倍增预处理
const int MAXN = 1e5 + 10; const int LOG = 20; vector<int> adj[MAXN]; int up[LOG][MAXN], depth[MAXN]; long long xor_sum[MAXN], x[MAXN]; // x[i]是城市i的x_i void dfs_lca(int u, int parent_u) { up[0][u] = parent_u; depth[u] = depth[parent_u] + 1; xor_sum[u] = xor_sum[parent_u] ^ x[u]; for (int v : adj[u]) { if (v != parent_u) { dfs_lca(v, u); } } } void init_lca(int root, int n) { memset(up, 0, sizeof(up)); depth[0] = 0; xor_sum[0] = 0; dfs_lca(root, 0); for (int k = 1; k < LOG; k++) { for (int u = 1; u <= n; u++) { up[k][u] = up[k-1][up[k-1][u]]; } } } int lca_query(int u, int v) { if (depth[u] < depth[v]) swap(u, v); // 提升u到与v同深度 for (int k = LOG-1; k >= 0; k--) { if (depth[u] - (1 << k) >= depth[v]) { u = up[k][u]; } } if (u == v) return u; // 共同提升到LCA for (int k = LOG-1; k >= 0; k--) { if (up[k][u] != up[k][v]) { u = up[k][u]; v = up[k][v]; } } return up[0][u]; }2. 可持久化字典树
const int MAX_NODE = 1e5 * 32; // 1e5节点,每个32位 int trie[MAX_NODE][2], cnt[MAX_NODE], version[MAXN]; int tot = 0; void insert(int &root, int pre_root, long long x) { root = ++tot; int cur = root; pre_root = version[pre_root]; // 父节点的版本 for (int i = 30; i >= 0; i--) { int bit = (x >> i) & 1; // 继承父节点的另一分支 trie[cur][1 - bit] = trie[pre_root][1 - bit]; // 新建当前分支 trie[cur][bit] = ++tot; // 移动指针 cur = trie[cur][bit]; pre_root = trie[pre_root][bit]; } } // 查询u到v路径上s^x_i的最大值,返回对应的x_i long long query_max_xor(int u, int v, int lca, long long s) { int root_u = version[u], root_v = version[v], root_lca = version[up[0][lca]]; long long res = 0; for (int i = 30; i >= 0; i--) { int bit = (s >> i) & 1; // 优先选1-bit分支(最大化异或),需确保该分支在路径中存在 if (trie[root_u][1 - bit] || trie[root_v][1 - bit] || trie[root_lca][1 - bit]) { res |= (1LL << i); bit = 1 - bit; } // 移动到下一位 root_u = trie[root_u][bit]; root_v = trie[root_v][bit]; root_lca = trie[root_lca][bit]; } return res ^ s; // 因res = s^x_k,故x_k = res ^ s } // 初始化可持久化字典树(root为1) void init_persistent_trie(int root, int n) { tot = 0; memset(trie, 0, sizeof(trie)); version[0] = 0; // DFS构建,parent[u]为u的父节点 function<void(int, int)> dfs = [&](int u, int parent_u) { insert(version[u], parent_u, x[u]); for (int v : adj[u]) { if (v != parent_u) { dfs(v, u); } } }; dfs(root, 0); }3. 带约束的区间最大异或
long long max_xor_with_limit(long long S, long long w) { long long res = 0, current = 0; for (int i = 30; i >= 0; i--) { int bit_S = (S >> i) & 1; int max_bit = (w >> i) & 1; // 尝试选1-bit_S(最大化异或) int try_bit = 1 - bit_S; if (try_bit <= max_bit) { // 若选try_bit,剩余位可自由选(0~2^i-1),直接更新res res |= (1LL << i); if (try_bit < max_bit) { // 剩余位可全选1,直接返回 res |= ((1LL << i) - 1); break; } current |= (try_bit << i); } else { // 只能选bit_S,current累加 current |= (bit_S << i); } } return res; }4. 主函数逻辑
int main() { ios::sync_with_stdio(false); cin.tie(0); int n, m; long long w; cin >> n >> m >> w; // 建图 for (int i = 0; i < n-1; i++) { int u, v; cin >> u >> v; adj[u].push_back(v); adj[v].push_back(u); } // 输入x_i(1-based) for (int i = 1; i <= n; i++) { cin >> x[i]; } // 预处理LCA和异或前缀和(根设为1) init_lca(1, n); // 预处理可持久化字典树 init_persistent_trie(1, n); // 处理m次查询 while (m--) { int u, v; long long s; cin >> u >> v >> s; // 步骤1:求LCA int l = lca_query(u, v); // 步骤2:查路径上s^x_i最大的x_k long long x_k = query_max_xor(u, v, l, s); // 步骤3:计算S long long total_xor = xor_sum[u] ^ xor_sum[v] ^ x[l]; long long S = total_xor ^ x_k; // 步骤4:求[0,w]中W^S的最大值 long long ans = max_xor_with_limit(S, w); cout << ans << '\n'; } return 0; }五、关键细节与复杂度分析
1. 细节说明
- 可持久化字典树的版本管理:
version[u]存储节点 对应的字典树根,确保路径查询时能正确合并父节点信息。 - 异或和计算的正确性:路径总异或和需补回 的 ,避免重复抵消;去掉 需再异或一次 ,利用异或“自反性”()。
- 区间约束的贪心逻辑:在
max_xor_with_limit中,若选择的位小于 对应位,剩余位可全选1,直接跳出循环,提升效率。
2. 复杂度分析
- 预处理:LCA ,可持久化字典树 ;
- 单次查询:LCA ,路径最大异或 ,区间最大异或 ;
- 总复杂度:,完全适配 的数据规模。
- 1
信息
- ID
- 4224
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者