目录
[1. 相反数](#1. 相反数)
[2. LowBit:提取最低位的 1](#2. LowBit:提取最低位的 1)
[3. Letter:判断十六进制数含不含字母](#3. Letter:判断十六进制数含不含字母)
[1. 树状数组是什么?](#1. 树状数组是什么?)
[2. 工作原理](#2. 工作原理)
[2.1 单点更新 add(i, delta)](#2.1 单点更新 add(i, delta))
[2.2 前缀和查询 sum(i)](#2.2 前缀和查询 sum(i))
[2.3 区间和](#2.3 区间和)
一、位运算
1. 相反数
对于一个有符号整数 x,其相反数可以通过 "取反加一" 得到:
cpp
-x = ~x + 1
-
~x:按位取反(包括符号位) -
+1:加一操作
以 4 位为例 :
x = 5 (0101) → ~x = 1010 → ~x+1 = 1011 → 即 -5 的补码。
2. LowBit:提取最低位的 1
LowBit(x) 返回一个整数,其二进制表示中只有 x 的最低位 1 被保留,其余位均为 0。
cpp
unsigned LowBit(unsigned x) {
return x & (-x);
}
数学推导
-
设
x的二进制形式为...100...0(末尾有k个 0,即最低位 1 在第k+1位)。 -
~x将该 1 变为 0,后面的 0 全变为 1,前面的位取反。 -
~x + 1使得后面的 1 进位,恰好将原来那个 1 的位置恢复为 1,而更低位置 0。 -
因此
x & (~x + 1) = 2^k。 -
而上述已经证明过 ~x+1 <=> -x。
3. Letter:判断十六进制数含不含字母
设一个十六进制数字的 4 位为x3x2x1x0(x3 是最高位,权值 8)。
字母范围 1010 ~ 1111 的二进制特点:
-
x3 = 1(最高位必须为 1,因为 10~15 最高位都是 1) -
且
(x2, x1)不能同时为 0 ------ 因为如果 x2=0, x1=0且 x3=1,则可能得到1000(8),1001(9),这是数字,不是字母。
所以逻辑表达式为:
cpp
is_letter = b3 AND (b2 OR b1)
我们需要对 32 位整数中的每 4 位,也就是8个十六进制数 同时应用上述逻辑。
使用掩码分别取出每个十六进制数的 x3, x2, x1:
-
x3(bit3)对应掩码0x88888888(1000 1000 ... 1000) -
x2(bit2)对应掩码0x44444444(0100 0100 ... 0100) -
x1(bit1)对应掩码0x22222222(0010 0010 ... 0010)
cpp
unsigned x1 = x & 0x22222222; // 取出每 4 位中的第 2 位(从右数,位权 2)
unsigned x2 = x & 0x44444444; // 取出每 4 位中的第 3 位(位权 4)
unsigned x3 = x & 0x88888888; // 取出每 4 位中的第 4 位(位权 8)
为了进行逻辑运算,还需要将x3、x2、x1对应位上的数字都提取出来。
-
将
x3右移 3 位,使每个半字节的b3移到 bit0 -
将
x2右移 2 位,使b2移到 bit0 -
将
x1右移 1 位,使b1移到 bit0
最终进行逻辑运算:
cpp
unsigned a = (x3 >> 3) & ((x2 >> 2) | (x1 >> 1));
a即是32位中每个十六进制数是否为字母的编码,1代表为字母,0代表为数字。
二、树状数组
1. 树状数组是什么?
树状数组是一个精巧的二进制分治结构,通过 lowbit 实现了快速跳跃。
其核心是维护一系列不同大小的块,使得任意前缀和都能由 O(log n) 个块拼成。
-
单点更新 :
add(index, delta)O(log n) -
前缀和查询 :
sum(index)O(log n) -
通过前缀和差得到区间和 :
sum(r) - sum(l-1)
2. 工作原理

假设原数组为 a[1..n]。
树状数组 c[1..n] 的定义是:
c[i] = sum( a[i - lowbit(i) + 1] , ... , a[i] )即从
i向前数lowbit(i)个元素的和。
2.1 单点更新 add(i, delta)
要将 a[i] 增加 delta,需要更新所有 包含 a[i] 的 c[j] 。
这些 j 恰好是:i, i+lowbit(i), i+lowbit(i)+lowbit(i+lowbit(i)), ... ≤ n。
原因:
c[j] 覆盖的区间是 (j-lowbit(j), j]。
若 j 包含 i,则 i > j-lowbit(j),即 j-i<lowbit(j)。
-
第一步:
j₁ = i + lowbit(i),lowbit(i) < lowbit(j₁)。 -
第二步:
j₂ = j₁ + lowbit(j₁),lowbit(i)+lowbit(j₁) < lowbit(j₂)。 -
依此类推,每一步累积的步长总和都小于下一步的 lowbit,从而保证
i始终在新节点的左边界右侧。
cpp
void Add(int index,int val) {
while (index<TArr.size()) {
TArr[index] += val;
index += LowBit(index);
}
}
void update(int index, int val) {
Add(index+1, val-Arr[index]);
Arr[index] = val;
}
2.2 前缀和查询 sum(i)
求 A[1] + ... + A[i]。
由 C 的定义,C[i] 已经包含了从 i-lowbit(i)+1 到 i 的和。
因此我们累加 C[i],然后令 i -= lowbit(i),继续累加,直到 i=0。
cpp
int sum(int i) {
int res = 0;
while (i > 0) {
res += C[i];
i -= lowbit(i);
}
return res;
}
2.3 区间和
cpp
int range_sum(int l, int r) {
return sum(r) - sum(l-1);
}