1 条题解
-
0
G. Your Loss 详细题解
题目重述
给定一棵 个节点的树,节点 有权值 。有 个查询,每个查询给出两个节点 和 。设从 到 的路径上的节点按顺序为 ,要求计算
其中 表示按位异或。输出每个查询的结果。
数据范围
- 个测试用例,。
- 单个测试用例中 ,所有测试用例的 之和 。
- ,因此 。
- ,所有测试用例的 之和 。
- 时间和内存限制:3 秒,256 MB。
核心思路
直接按路径遍历每个节点会超时()。需要将路径拆分为若干连续段,并快速计算每段内形如 的和。由于树结构,可以采用树链剖分将任意路径分解为 条重链上的连续区间。对于每个区间,我们需要高效计算该区间内所有节点值与一个等差数列 进行异或的和。
二进制拆位
因为异或运算按位独立,我们可以逐位计算贡献:
$$\sum_{j=1}^{L} (x_j \oplus y_j) = \sum_{b=0}^{B-1} 2^b \cdot \big( \#\{j \mid (x_j)_b \neq (y_j)_b\} \big) $$其中 $B = \lceil \log_2(\max a_i + \text{路径长度})\rceil \le 20$(因为 ,,所以 位足够)。
对于一段长度为 的区间,节点值序列为 ,对应下标 从 到 (全局路径中的实际下标为 )。对于第 位,设:
- 为区间内节点值的第 位为 的个数;
- 为区间内 的第 位为 的个数(即整数 中第 位为 的个数)。
则第 位不同的个数为:
$$cnt_1 \cdot (L - ones) + (L - cnt_1) \cdot ones = cnt_1 \cdot L + (L - 2cnt_1) \cdot ones $$因此该位对总和的贡献为 乘以该值。
快速计算 和
- :对于树链剖分后的每条重链,我们可以预处理该链上所有节点值的二进制前缀和。即对于链上的节点序列 ,预计算
pre[bit][i]表示前 个节点中第 位为 的个数。这样任意区间 内的 可在 内得到:pre[bit][r+1] - pre[bit][l]。 - :需要计算等差数列 中第 位为 的个数。这是一个数学问题,可以用公式快速求出。设 ,则一个非负整数 的第 位为 当且仅当 。因此区间 内第 位为 的个数为: 其中 $f(x) = \left\lfloor\frac{x+1}{T}\right\rfloor \cdot \frac{T}{2} + \max(0, (x+1)\bmod T - \frac{T}{2})$。 该计算为 。
处理路径方向
树链剖分将路径分解为若干段:
- 从 向上到 LCA(不含 LCA)的若干重链段,这些段在重链上的顺序是从深到浅,但节点值的顺序与重链存储方向相反。由于我们只关心 (与顺序无关),可以直接使用正向区间统计节点值。但注意,此时区间对应全局下标 是递增的,而我们统计 时不受顺序影响,所以 直接取正向区间内节点即可。
- 从 LCA 向下到 的若干重链段,这些段在重链上是从浅到深,与存储方向一致,直接取正向区间。
我们需要记录整个路径上每个节点对应的全局下标 (从 开始)。对于向上段,由于路径是逆向的,但计算贡献时 仍然是顺序递增的,因此我们可以先计算当前段长度 ,那么该段的起始下标为当前全局下标 ,节点顺序是逆向的。因为异或运算对顺序不敏感(只是每个节点与不同 异或),我们的统计方式已经通过 和 分离了节点值和下标的影响,所以可以直接用正向区间节点值()和连续下标区间 计算,而不需要真正反转节点序列。
算法流程
- 预处理:
- 对树进行轻重链剖分,得到每个节点的父节点、深度、子树大小、重儿子、链顶、在重链中的位置。
- 记录每条重链的节点列表(从链顶到链底)。
- 对于每条重链,对所有 ,计算前缀和
pre[bit][i]( 从 到 )。
- 处理查询:
- 输入 。
- 求出 。
- 初始化当前全局下标
cur = 0,答案ans = 0。 - 处理向上段( 到 ,包括 ):
- 将路径从 向上跳重链,直到到达 所在链。
- 对每一段(链顶到当前节点),设该段在链上的区间为 ( 为链顶位置, 为当前节点位置),长度为 。
- 调用函数
calc(chain_id, lpos, rpos, cur),该函数:- 对于每个 ,计算 (利用前缀和),(利用数学公式),得到该位贡献。
- 将贡献累加到
ans。
- 更新
cur += len,然后 继续循环。
- 处理向下段( 到 ,不包括 ):
- 从 向上收集所有需要经过的重链段,但顺序是反向的(因为从 向下到 才是正向顺序)。
- 先将所有段(从 到 的路径)收集到一个数组
segs中,每段存储(chain_id, lpos, rpos),其中 是段内较浅的一端(靠近 ), 是较深的一端(靠近 )。收集时注意 本身不包含在内,所以最后一段从 开始。 - 将
segs反转,使得顺序变为从 向下到 。 - 遍历
segs,对每个段调用calc,每次更新cur。
- 输出
ans。
复杂度
- 预处理:,其中 。
- 每个查询:路径分解为 段,每段计算 位,每位的计算为 ,因此单次查询 。对于 ,总复杂度 ,在给定范围内可接受。
正确性说明
- 树链剖分保证任意路径可以被 条重链上的连续区间覆盖。
- 因为异或运算按位独立,并且我们正确统计了每一位上节点值与下标不同的个数,因此累加后得到准确的总和。
- 公式计算 时使用了闭区间上的计数,精确无误。
- 向上段虽然节点顺序与链存储顺序相反,但 只依赖于节点集合(与顺序无关),而 依赖于顺序递增的下标,所以直接使用正向区间计算是正确的(因为正向区间对应的节点集合与逆向集合相同,只是排列不同,但下标 的等差数列仍按全局顺序递增,不影响统计结果)。
实现细节
- 由于 ,且路径长度最多 ,所以 足够。
- 为了节省内存,可以只存储每条重链的 个前缀和数组,每条链的长度总和为 ,总空间 ,约 个整数,内存约 MB,在限制内。
- 数学公式中需要用到 位整数(因为 可能达到 级别,但仍在 内),使用
long long即可。
总结
本题是树上路径异或求和问题,利用树链剖分转化为区间问题,再利用二进制拆位和数学公式快速计算每个区间内节点与等差数列的异或和。通过预处理每条重链上各二进制位的前缀和,可以在 时间内回答每个查询,满足大数据范围的要求。
- 1
信息
- ID
- 6936
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者