UVa 13277 XOR Path

题目分析

问题描述

给定一棵无根带权树,节点数为 nnn (n≤100000n \leq 100000n100000),边权为 161616 位无符号整数(取值范围为 000216−12^{16}-12161)。对于每个整数 xxx (0≤x≤216−10 \leq x \leq 2^{16}-10x2161),需要计算树中有多少对不同的无序节点 (u,v)(u,v)(u,v),使得它们之间的路径异或和等于 xxx

路径异或和定义为连接两节点的唯一路径上所有边权的异或和。

关键观察

  1. 异或路径性质
    在树结构中,如果选定任意节点作为根,定义 dist[u]dist[u]dist[u] 为节点 uuu 到根节点的路径异或和,那么任意两个节点 uuuvvv 之间的路径异或和可以表示为:
    dist[u]⊕dist[v] dist[u] \oplus dist[v] dist[u]dist[v]
    这是因为从 uuuvvv 的路径可以看作 u→root→vu \to root \to vurootv,而公共部分 root→LCA(u,v)root \to LCA(u,v)rootLCA(u,v) 在异或操作中会相互抵消。

  2. 问题转化
    原问题转化为:给定数组 dist[1..n]dist[1..n]dist[1..n],计算对于每个 xxx,满足 dist[i]⊕dist[j]=xdist[i] \oplus dist[j] = xdist[i]dist[j]=xi<ji < ji<j 的无序对 (i,j)(i,j)(i,j) 的数量。

  3. 频数统计方法
    定义频数数组 freqfreqfreq,其中 freq[p]freq[p]freq[p] 表示值 pppdistdistdist 数组中出现的次数。那么:
    count[x]=∑p⊕q=xfreq[p]⋅freq[q] count[x] = \sum_{p \oplus q = x} freq[p] \cdot freq[q] count[x]=pq=xfreq[p]freq[q]
    这实际上是一个异或卷积问题。

  4. 调整计算
    上述公式计算的是有序对的数量,需要调整为无序对:

    • x=0x = 0x=0 时:count[0]=(freq∗freq)[0]−n2count[0] = \frac{(freq \ast freq)[0] - n}{2}count[0]=2(freqfreq)[0]n
    • x≠0x \neq 0x=0 时:count[x]=(freq∗freq)[x]2count[x] = \frac{(freq \ast freq)[x]}{2}count[x]=2(freqfreq)[x]
      这里减去 nnn 是因为 i=ji = ji=j 的情况会产生 nnn 个贡献(p⊕p=0p \oplus p = 0pp=0),除以 222 是因为我们只需要无序对。

算法选择

  • 使用深度优先搜索(DFS\texttt{DFS}DFS)计算每个节点到根节点的异或和,时间复杂度 O(n)O(n)O(n)
  • 使用快速沃尔什-哈达玛变换(FWHT\texttt{FWHT}FWHT)计算异或卷积,时间复杂度 O(Mlog⁡M)O(M \log M)O(MlogM),其中 M=216M = 2^{16}M=216

该算法总时间复杂度为 O(n+Mlog⁡M)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;
}

代码说明

  1. 数据结构

    • treeAdjacency:存储树的邻接表,每个元素为 (邻居节点, 边权)
    • xorDistance:存储每个节点到根节点的异或和
    • frequency:频数数组,记录每个异或值出现的次数
    • convolutionResult:存储异或卷积的结果
  2. 核心函数

    • performDFS:深度优先搜索计算每个节点到根节点的异或和
    • fastWalshHadamardTransform:实现快速沃尔什-哈达玛变换,用于计算异或卷积
  3. 算法流程

    • 读取输入并构建树的邻接表
    • 通过 DFS\texttt{DFS}DFS 计算所有节点到根节点的异或和
    • 统计每个异或值的出现频数
    • 使用 FWHT\texttt{FWHT}FWHT 计算频数数组的异或卷积
    • 根据卷积结果计算并输出每个 xxx 对应的路径数量
  4. 注意事项

    • 输出格式严格按照题目要求,每个测试用例输出以 “Case X:\texttt{Case X:}Case X:” 开头
    • 对于 x=0x = 0x=0 的情况需要特殊处理,减去 i=ji = ji=j 的无效情况
    • 所有结果除以 222 将有序对转换为无序对

该解法充分利用了树路径异或的性质和快速变换算法,在合理的时间复杂度内解决了大规模数据的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值