考虑 p 和 r 在二进制下从高到低第一个不同的位置 d。除去 p = r 的情况,d 一定存在。又因为 p \leq r,所以 p 的第 d 位为 0,r 的第 d 位 1。如果 r 在低 d - 1 位(第 0\sim d - 1 位)还有 1,那么 l = r - \mathrm{lb}(r) 的第 d 位仍为 1,和 l < p 矛盾。因此,r 的低 d - 1 位全部为 0,则 r - \mathrm{lb}(r) 的低 d 位为 0。此时若 p 在低 d - 1 位还有 1,则符合 l < p 的条件,否则 l = p,不符合条件。
这是合法的例子(d = 3):
\begin{cases}
r & = (1011 {\color{red} 1} {\color{blue} 000} )_2 \\
l & = (1011 0000)_2 \\
p & = (1011 {\color{red} 0} 00 {\color{green} 1})_2
\end{cases}
这是不合法的例子:
\begin{aligned}
\begin{cases}
r & = (1011 {\color{red} 1} 00 {\color{blue} 1} )_2 \\
l & = (1011 {\color{red} 1} 000)_2 \\
p & = (1011 {\color{red} 0} 001)_2
\end{cases}
\\
\\
\begin{cases}
r & = (1011 {\color{red} 1} {\color{blue} 000} )_2 \\
l & = (1011 0000)_2 \\
p & = (1011 {\color{red} 0} {\color{green} 000})_2
\end{cases}
\end{aligned}
第一个例子因为 r 在低 d - 1 位还有 1,所以 l 的第 d 位为 1,l > p;第二个例子因为 p 在低 d - 1 位没有 1,所以 l = p。
根据条件,我们可以直接构造所有 p 的祖先 r:考虑 p 的某个 0 位,且存在 1 位低于该位,将该位变成 1,其低位全部变成 0,就是一个合法的 r。相当于直接枚举 d。
考虑给 d_x 加上 v,在 y 处查询前缀和 s_y。d_x 加了 v 使得 a_{x\sim y} 全部加了 v,所以 s_y 加了 (y - x + 1) v。将贡献拆成 (y + 1)v 和 -xv。我们用两个树状数组 c_1, c_2,分别维护修改值 v 的一阶前缀和,和修改值 v 乘以修改位置 x 的一阶前缀和,那么 y 处的二阶前缀和就等于修改值在 y 处的一阶前缀和乘以 y + 1,减去修改值乘以修改位置在 y 处的一阶前缀和。
对组合数较熟悉的同学已经发现,矩阵第 i 行第 j 列就是 \binom {i + j} {i}。推导过程不难,归纳即可。
综上,对于 k 阶前缀和,x 处加 1 对 y 处的查询产生的贡献为 \binom {y - x + k - 1} {k - 1}。理解方式:考虑贡献路径 p_0 = x\leq p_1 \leq \cdots \leq p_k = y,每一个这样的 \{p\} 都对应 1 的贡献。换言之,建出这样一张图:对于任意 x\leq i\leq y,i 向 [i, y] 连边,从 x 恰好走 k 步到 y 的方案数就是贡献系数。这是经典问题,相当于在 y - x 个空隙放入 k - 1 个插板,两个插板可以放在不同的空隙;也相当于将 y - x +1 个球装入 k - 1 个不同的盒子,可以有盒子为空。有方案数 \binom {y - x + k - 1} {k - 1}。
例如,当 $k = 3$ 时,$\binom {y - x + 2} {2} = \frac 1 2 (y - x + 2) (y - x + 1)$。提出 $\frac 1 2$,展开得到 $y ^ 2 + x ^ 2 - 2xy + 3y - 3x + 2$,即 $x ^ 2 + (-2y - 3)x ^ 1 + (y ^ 2 + 3y + 2)x ^ 0$。因此:
- 求出 $vx ^ 0$ 即 $v$ 在 $y$ 处的前缀和,乘以 $y ^ 2 + 3y + 2$。
- 求出 $vx ^ 1$ 在 $y$ 处的前缀和,乘以 $-2y - 3$。
- 求出 $vx ^ 2$ 在 $y$ 处的前缀和,乘以 $1$。
将上述三个结果相加即为所求。
时间复杂度 $\mathcal{O}(kq\log n)$,其中 $k$ 表示前缀和阶数,$q$ 表示操作次数, $n$ 表示序列长度。
### 2.2 二维 BIT
本质上是树状数组套树状数组,用二维数组维护。它可以直接做单点加矩形查询,且矩形两维的左边界都是 $1$。差分一下就可以对任意矩形求和。
对于矩形加矩形求和,即维护二维差分数组的二阶二维前缀和。类似地,考虑 $(i, j)$ 处对二维差分数组的修改对 $(x, y)$ 处查询二维前缀和的贡献。给 $d_{i, j}$ 加 $v$ 使得 $a_{i\sim x, j\sim y}$ 全部加了 $v$,所以 $s_{x, y}$ 加了 $(x -i + 1) (y - j + 1) v$。拆开变成 $(x + 1)(y + 1)v - (y + 1)iv - (x + 1)jv + ijv$。用四个树状数组分别维护 $v$,$iv$,$jv$ 和 $ijv$ 的一阶二维前缀和即可。
时间复杂度 $\mathcal{O}(q\log n\log m)$。
[模板题](https://www.luogu.com.cn/problem/P4514) 代码:
```cpp
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 2050;
char s;
int n, m, c1[N][N], c2[N][N], c3[N][N], c4[N][N];
void add(int x, int y, int v) {
int v2 = x * v, v3 = y * v, v4 = x * y * v;
for(int i = x; i <= n; i += i & -i)
for(int j = y; j <= m; j += j & -j)
c1[i][j] += v, c2[i][j] += v2, c3[i][j] += v3, c4[i][j] += v4;
}
int query(int x, int y) {
int s1 = 0, s2 = 0, s3 = 0, s4 = 0;
for(int i = x; i; i -= i & -i)
for(int j = y; j; j -= j & -j)
s1 += c1[i][j], s2 += c2[i][j], s3 += c3[i][j], s4 += c4[i][j];
return (x + 1) * (y + 1) * s1 - (y + 1) * s2 - (x + 1) * s3 + s4;
}
int main() {
cin >> s >> n >> m;
while(cin >> s) {
if(s == 'L') {
int a, b, c, d, v;
cin >> a >> b >> c >> d >> v;
add(a, b, v), add(c + 1, b, -v), add(a, d + 1, -v), add(c + 1, d + 1, v);
}
else {
int a, b, c, d;
cin >> a >> b >> c >> d;
cout << query(c, d) + query(a - 1, b - 1) - query(a - 1, d) - query(c, b - 1) << "\n";
}
}
return 0;
}
```
### 2.3 后缀 BIT
交换查询和修改的跳转方式就是后缀 BIT 了。
```cpp
int n, c[N];
void add(int x, int v) {while(x) c[x] += v, x -= x & -x;}
int query(int x) {int s = 0; while(x <= n) s += c[x], x += x & -x; return s;}
int query(int l, int r) {return query(l) - query(r + 1);}
```
可以这样思考:在前缀 BIT 中,对于 $x \leq y$,一个 $x$ 处的更新对 $y$ 处的查询产生了贡献。类似地,在后缀 BIT 中,一个 $y$ 处的更新对 $x$ 处的查询产生了贡献。
$x$ 不断增大的操作只会影响所有位置不小于 $x$ 的信息(或和这样的信息有关),$x$ 不断减小的操作只会影响所有位置不大于 $x$ 的信息(或和这样的信息有关)。
对于可减信息,用前缀后缀 BIT 维护是一样的。后缀 BIT 的用途在于,当信息不可减且询问具有良好性质(询问后缀,或可以转化为询问后缀)时,能够不翻转序列地维护修改和查询,避免因翻转序列产生的大量下标变换使得代码写挂。
### 参考资料
**第一章**
- [补码 —— 百度百科](https://baike.baidu.com/item/补码/6854613?fr=aladdin)。