动态开点线段树优化

· · 个人记录

压缩

:::info[题目]

时间限制:1000 ms
空间限制:64 MB

题目描述

n = 2^{63}

如题,已知一个长度为 n 的数列 \{a_i\}0 \leq i < n),初始时 a 序列满足 a_i = 0。你需要进行下面两种操作:

  1. 将某一个数改为 k
  2. 求出某区间每一个数的异或和,并将结果写入 L
## 输入格式 第一行包含一个整数 $m$,表示操作的总个数。 接下来 $m$ 行每行包含 $3$ 个整数,表示一个操作。将每个数异或 $L$ 后,内容具体如下: 1. `1 i k`:将 $a_i$ 改为 $k$。 2. `2 l r`:输出 $a_l \sim a_r$ 内每个数的异或和,并将结果写入 $L$。 比如,输入了 $a,b,c$,则其实际值为$a\oplus L,b\oplus L,c\oplus L$。 ## 输出格式 输出包含若干行整数,即为所有操作 2 的结果。 ## 说明/提示 保证 $1 \le m\le {10}^6$,$0 \le l \le r <n$,$0 \le k < n$。] ::: 定义高度为与叶子的距离,叶子的高度为 0. 对于每个节点,维护其左子节点(含)下最高的节点;右同理. ```cpp typedef std::size_t S; struct No { S L, R, x; No *c[2]; }; #define lc (p->c[0]) #define rc (p->c[1]) ``` 宏定义一会有用。 ### 修改 递归修改 $a_i \leftarrow k$。当递归到一个节点 $p$ 时: - 判断是否可以直接修改; - 判断在哪侧 $j$; - 如果 $j$ 不存在,创建; - 否则如果 $i$ 在 $j$ 内,递归; - 否则创建其 LCA 并将两点放在其两侧(一定在两侧)。 ```cpp void pushup(No* p) { p->x = lc->x + rc->x; } void update(No* p, S i, S k) { if (p->L == p->R - 1) { p->x = k; return; } S L = p->L, r = p->R; S mid = L + ((R - L) >> 1); No*& s = p->c[i >= mid]; if (s) { if (s->L <= i && i < s->R) update(s, i, k); else { S t = SIZE_MAX >> __builtin_clzll(s->L ^ i), u = ~t & i; No* v = s; if (i >= v->R) s = new No {u, u + t + 1, 0, {v, new No{i, i, k}}}; else s = new No {u, u + t + 1, 0, {new No{i, i, k}, v}}; pushup(s); } } else { s = new No {i, i, k}; } } ```