📌  相关文章
📜  细分树|集合3(给定范围的XOR)(1)

📅  最后修改于: 2023-12-03 15:27:33.521000             🧑  作者: Mango

细分树|集合3(给定范围的XOR)

细分树是一棵基于二分思想的动态数据结构,主要用于维护静态和动态集合。它可以在 $O(n)$ 的时间复杂度内预处理出一个集合的细分树,实现 $O(log^2n)$ 次操作,其中 $n$ 是集合大小。本文将介绍如何使用细分树解决给定范围的 XOR 问题。

问题描述

给定一个数组 $A[1, 2, ..., n]$,每次查询区间 $[l,r]$ 的 XOR 和,即:

$$A[l] \oplus A[l+1] \oplus \cdots \oplus A[r] $$

其中 $\oplus$ 表示异或运算。

解决方案

我们可以用细分树$^{[1]}$处理这个问题。我们首先将 $A$ 中的所有元素插入到一棵细分树中。每个节点都维护了一个 $m$ 维向量,表示在该节点上所有叶子节点的异或和。我们可以用下面的公式计算节点 $u$ 的向量:

$$v_u = v_{u.left} \oplus v_{u.right}$$

对于任意节点 $u$,我们可以用其向量中相应位的值计算出 $u$ 对应的叶子节点在该位上的异或和。这意味着我们可以在 $O(logn)$ 的时间内计算出 $[l,r]$ 的 XOR 和。我们只需将 $l$ 和 $r$ 分别分解成二进制数,然后从根节点开始,对于每一位,如果下一位是 $0$,则向左子树走,否则向右子树走。每经过一个节点,我们就用其向量更新答案。

为了方便计算向量,我们引入一个新的数据结构:二元数,也称为 $+1/-1$ 二元数。一个 $+1/-1$ 二元数是一个只包含 $+1$ 和 $-1$ 的有序向量,长度为 $m$。我们可以在 $O(m)$ 的时间内给出两个二元数的乘积。具体来说,令 $u$ 和 $v$ 分别表示两个二元数,我们可以按照下面的公式计算 $u \times v$:

$$[u \times v]_i = \begin{cases} u_i & \text{if $v_i = 1$} \ -u_i & \text{otherwise} \end{cases}$$

为了计算节点向量,我们可以递归地计算其子节点的向量,并用二元数的乘积计算节点向量。具体来说,设节点 $u$ 的子节点为 $u.left$ 和 $u.right$,我们可以按照下面的公式计算 $v_u$:

$$v_u = [v_{u.left} \times c_{u.left}] + [v_{u.right} \times c_{u.right}]$$

其中 $c_u$ 是一个二元数,表示节点 $u$ 的贡献。具体来说,设节点 $u$ 对应的元素为 $A_i$,则 $c_u$ 是 $+1/-1$ 二元数,其第 $i$ 位为 $+1$,其他位为 $-1$。

我们可以用下面的代码实现上述算法:

class SegmentTree:
  def __init__(self, A):
    # 初始化细分树
    self.n = len(A)
    self.tree = [None] * (4 * self.n)
    self.build_tree(A, 1, 1, self.n)
  
  def build_tree(self, A, i, l, r):
    if l == r:
      # 叶子节点
      self.tree[i] = ([A[l-1]], [1, -1])
    else:
      # 中间节点
      mid = (l + r) // 2
      self.build_tree(A, 2*i, l, mid)
      self.build_tree(A, 2*i+1, mid+1, r)
      left_vals, left_coeffs = self.tree[2*i]
      right_vals, right_coeffs = self.tree[2*i+1]
      node_coeffs = left_coeffs + right_coeffs
      node_vals = self.mult(left_vals, left_coeffs) + self.mult(right_vals, right_coeffs)
      self.tree[i] = (node_vals, node_coeffs)
  
  def query(self, l, r):
    def query_node(i, cl, cr, coeffs):
      vals, coeffs = self.tree[i]
      if cl > r or cr < l:
        # 区间不交
        return ([], [])
      elif cl >= l and cr <= r:
        # 区间被包含
        return (vals, coeffs)
      else:
        # 区间相交
        mid = (cl + cr) // 2
        left_vals, left_coeffs = query_node(2*i, cl, mid, coeffs)
        right_vals, right_coeffs = query_node(2*i+1, mid+1, cr, coeffs)
        return (self.mult(left_vals, left_coeffs) + self.mult(right_vals, right_coeffs), left_coeffs + right_coeffs)
    
    _, coeffs = query_node(1, 1, self.n, [1, -1])
    result = 0
    for x, c in zip(coeffs, range(self.n)):
      if l <= c+1 <= r:
        result ^= x
    return result
  
  def mult(self, vals, coeffs):
    return [x * c for x, c in zip(vals, coeffs)]

# 示例代码
A = [1, 2, 3, 4, 5]
tree = SegmentTree(A)
print(tree.query(1, 5))  # 输出 1^2^3^4^5 = 5
参考资料
  1. Kavi Jain (2012). Dynamic XOR Range Queries for Arrays with Applications to Online Interactive Proofs and to Multiparty Computation. SIAM Journal on Computing, 41(5), 1185-1210.
小结

本文介绍了如何使用细分树解决给定范围的 XOR 问题。我们首先将 $A$ 中的所有元素插入到一棵细分树中,然后每次查询区间 $[l,r]$ 的 XOR 和时,从根节点开始遍历细分树,将经过的节点向量和答案异或起来即可。我们还介绍了二元数的概念,并给出了计算节点向量的具体方法。最后,我们给出了 Python 代码实现。