题目分析
问题描述
给定一棵无根带权树,节点数为 nnn (n≤100000n \leq 100000n≤100000),边权为 161616 位无符号整数(取值范围为 000 到 216−12^{16}-1216−1)。对于每个整数 xxx (0≤x≤216−10 \leq x \leq 2^{16}-10≤x≤216−1),需要计算树中有多少对不同的无序节点 (u,v)(u,v)(u,v),使得它们之间的路径异或和等于 xxx。
路径异或和定义为连接两节点的唯一路径上所有边权的异或和。
关键观察
-
异或路径性质
在树结构中,如果选定任意节点作为根,定义 dist[u]dist[u]dist[u] 为节点 uuu 到根节点的路径异或和,那么任意两个节点 uuu 和 vvv 之间的路径异或和可以表示为:
dist[u]⊕dist[v] dist[u] \oplus dist[v] dist[u]⊕dist[v]
这是因为从 uuu 到 vvv 的路径可以看作 u→root→vu \to root \to vu→root→v,而公共部分 root→LCA(u,v)root \to LCA(u,v)root→LCA(u,v) 在异或操作中会相互抵消。 -
问题转化
原问题转化为:给定数组 dist[1..n]dist[1..n]dist[1..n],计算对于每个 xxx,满足 dist[i]⊕dist[j]=xdist[i] \oplus dist[j] = xdist[i]⊕dist[j]=x 且 i<ji < ji<j 的无序对 (i,j)(i,j)(i,j) 的数量。 -
频数统计方法
定义频数数组 freqfreqfreq,其中 freq[p]freq[p]freq[p] 表示值 ppp 在 distdistdist 数组中出现的次数。那么:
count[x]=∑p⊕q=xfreq[p]⋅freq[q] count[x] = \sum_{p \oplus q = x} freq[p] \cdot freq[q] count[x]=p⊕q=x∑freq[p]⋅freq[q]
这实际上是一个异或卷积问题。 -
调整计算
上述公式计算的是有序对的数量,需要调整为无序对:- 当 x=0x = 0x=0 时:count[0]=(freq∗freq)[0]−n2count[0] = \frac{(freq \ast freq)[0] - n}{2}count[0]=2(freq∗freq)[0]−n
- 当 x≠0x \neq 0x=0 时:count[x]=(freq∗freq)[x]2count[x] = \frac{(freq \ast freq)[x]}{2}count[x]=2(freq∗freq)[x]
这里减去 nnn 是因为 i=ji = ji=j 的情况会产生 nnn 个贡献(p⊕p=0p \oplus p = 0p⊕p=0),除以 222 是因为我们只需要无序对。
算法选择
- 使用深度优先搜索(DFS\texttt{DFS}DFS)计算每个节点到根节点的异或和,时间复杂度 O(n)O(n)O(n)
- 使用快速沃尔什-哈达玛变换(FWHT\texttt{FWHT}FWHT)计算异或卷积,时间复杂度 O(MlogM)O(M \log M)O(MlogM),其中 M=216M = 2^{16}M=216
该算法总时间复杂度为 O(n+MlogM)O(n + M \log M)O(n+MlogM),在题目约束下完全可行。
代码实现
// XOR Path
// UVa ID: 13277
// Verdict: Accepted
// Submission Date: 2025-10-24
// UVa Run Time: 0.280s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
// 常量定义
const int MAX_NODE_COUNT = 100000; // 最大节点数
const int MAX_VALUE_RANGE = 1 << 16; // 值域范围 2^16
// 全局变量声明
vector<pair<int, int>> treeAdjacency[MAX_NODE_COUNT + 5]; // 树的邻接表存储
int xorDistance[MAX_NODE_COUNT + 5]; // 存储每个节点到根节点的异或距离
long long frequency[MAX_VALUE_RANGE]; // 频数数组,记录每个异或值的出现次数
long long convolutionResult[MAX_VALUE_RANGE]; // 卷积结果数组
/**
* 深度优先搜索函数,计算每个节点到根节点的异或和
* @param currentNode 当前处理的节点
* @param parentNode 父节点,用于避免重复访问
* @param currentXor 当前路径的异或累积值
*/
void performDFS(int currentNode, int parentNode, int currentXor) {
xorDistance[currentNode] = currentXor; // 记录当前节点到根节点的异或和
// 遍历当前节点的所有邻居
for (auto& edge : treeAdjacency[currentNode]) {
int neighborNode = edge.first; // 邻居节点
int edgeWeight = edge.second; // 边权值
// 避免回溯到父节点
if (neighborNode != parentNode) {
// 递归计算子节点的异或和
performDFS(neighborNode, currentNode, currentXor ^ edgeWeight);
}
}
}
/**
* 快速沃尔什-哈达玛变换,用于计算异或卷积
* @param array 输入数组,同时也会被修改为变换结果
* @param arraySize 数组大小,必须是2的幂次
* @param invertTransform 是否为逆变换
*/
void fastWalshHadamardTransform(long long array[], int arraySize, bool invertTransform) {
// 分治进行变换
for (int segmentLength = 1; 2 * segmentLength <= arraySize; segmentLength <<= 1) {
for (int startIndex = 0; startIndex < arraySize; startIndex += 2 * segmentLength) {
for (int offset = 0; offset < segmentLength; offset++) {
// 获取当前分段的两个值
long long firstValue = array[startIndex + offset];
long long secondValue = array[startIndex + offset + segmentLength];
// 正变换:和与差
array[startIndex + offset] = firstValue + secondValue;
array[startIndex + offset + segmentLength] = firstValue - secondValue;
}
}
}
// 如果是逆变换,需要除以数组大小进行归一化
if (invertTransform) {
for (int i = 0; i < arraySize; i++) {
array[i] /= arraySize;
}
}
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int testCaseCount; // 测试用例数量
cin >> testCaseCount;
// 处理每个测试用例
for (int caseNumber = 1; caseNumber <= testCaseCount; caseNumber++) {
int nodeCount; // 当前测试用例的节点数
cin >> nodeCount;
// 初始化邻接表
for (int i = 1; i <= nodeCount; i++) {
treeAdjacency[i].clear();
}
// 读入树的边信息
for (int i = 0; i < nodeCount - 1; i++) {
int firstNode, secondNode, weight;
cin >> firstNode >> secondNode >> weight;
// 构建无向图
treeAdjacency[firstNode].emplace_back(secondNode, weight);
treeAdjacency[secondNode].emplace_back(firstNode, weight);
}
// 从节点 1 开始 DFS 计算所有节点到根节点的异或和
performDFS(1, -1, 0);
// 初始化频数数组
memset(frequency, 0, sizeof(frequency));
// 统计每个异或值的出现次数
for (int i = 1; i <= nodeCount; i++) {
frequency[xorDistance[i]]++;
}
// 将频数数组复制到卷积结果数组进行变换
memcpy(convolutionResult, frequency, sizeof(frequency));
// 执行正变换
fastWalshHadamardTransform(convolutionResult, MAX_VALUE_RANGE, false);
// 点乘:计算频数数组的平方(在变换域中)
for (int i = 0; i < MAX_VALUE_RANGE; i++) {
convolutionResult[i] *= convolutionResult[i];
}
// 执行逆变换,得到卷积结果
fastWalshHadamardTransform(convolutionResult, MAX_VALUE_RANGE, true);
// 输出结果
cout << "Case " << caseNumber << ":\n";
for (int x = 0; x < MAX_VALUE_RANGE; x++) {
long long pathCount;
if (x == 0) {
// 对于 x=0,需要减去 i=j 的情况(n个),然后除以 2 得到无序对
pathCount = (convolutionResult[x] - nodeCount) / 2;
} else {
// 对于 x≠0,直接除以 2 得到无序对
pathCount = convolutionResult[x] / 2;
}
cout << pathCount << "\n";
}
}
return 0;
}
代码说明
-
数据结构
treeAdjacency:存储树的邻接表,每个元素为(邻居节点, 边权)xorDistance:存储每个节点到根节点的异或和frequency:频数数组,记录每个异或值出现的次数convolutionResult:存储异或卷积的结果
-
核心函数
performDFS:深度优先搜索计算每个节点到根节点的异或和fastWalshHadamardTransform:实现快速沃尔什-哈达玛变换,用于计算异或卷积
-
算法流程
- 读取输入并构建树的邻接表
- 通过 DFS\texttt{DFS}DFS 计算所有节点到根节点的异或和
- 统计每个异或值的出现频数
- 使用 FWHT\texttt{FWHT}FWHT 计算频数数组的异或卷积
- 根据卷积结果计算并输出每个 xxx 对应的路径数量
-
注意事项
- 输出格式严格按照题目要求,每个测试用例输出以 “Case X:\texttt{Case X:}Case X:” 开头
- 对于 x=0x = 0x=0 的情况需要特殊处理,减去 i=ji = ji=j 的无效情况
- 所有结果除以 222 将有序对转换为无序对
该解法充分利用了树路径异或的性质和快速变换算法,在合理的时间复杂度内解决了大规模数据的问题。


1784

被折叠的 条评论
为什么被折叠?



