树状数组 - 高级数据结构
引入
数列操作
本题是 树状数组
模板题,这个算法最大的优点是查询速度很快
col1 | 修改(add) | 求和查询(query) | 构造(init) |
---|---|---|---|
树状数组 | \(O(\log n)\) | \(O(\log n)\) | \(O(n)\small\color{ForestGreen}{//\ 读入数据}\) |
数组 | \(O(1)\) | \(O(n)\) | \(O(n\log n)\small\color{ForestGreen}{//\ n次修改}\) |
所以树状数组适合数据范围大,查询次数多的时候使用
实现
lowbit()
见多识广的同学都知道这个函数,它的目的是求出一个数二进制中最低位的1的位置,并将位置转化成十进制
举个例子:
\[\begin{align}
lowbit(22)_{10} &= lowbit(10110)_2\\
&= (10)_2\\
&= 2
\end{align}
\]
如何实现lowbit()呢?
\[首先,将原数取反:\\
~10110 = 01001\\
再加一:\\
01001+1 = 01010\\
将新数与原数进行与运算(\&)\\
\begin{align}
\ \ 01010\nonumber\\
\&\ 10110\nonumber\\
————\nonumber\\
00010\nonumber\\
也就是2\nonumber
\end{align}
\]
众所周知,补码本质就是原码的反码加一
于是我们可以简化代码:
int lowbit(int x)
{
return x & -x;
}
树状数组
结构:树
树状数组其实是前缀和
的变形,或者说,前缀和本身就是没有修改操作的树状数组。
聪明绝顶(物理)的科学家发现C数组(存树)的二进制下标对应了它所涵盖的数的数量,得出公式:
\[C[i]涵盖数的数量 = lowbit(i)
\]
根据一根头发的观察发现,要找到一个点的父亲,我们需要将此点的二进制下标加上\(lowbit(下标)\)
即
\[father_{C[i]} = i + lowbit(i)
\]
举个栗子:
\[∵3 = (011)_2\\
∴\left\{
\begin{align}
father_3 &= lowbit(011) + (011)_2\nonumber \\
&= (1 + 011)_2\nonumber\\
&= (100)_2 \nonumber\\
&= 4\nonumber
\end{align}\right.\\
∴father_3 = 4\\
\]
我们前面说过,树状数组支持两种操作(修改和求和),下方展开说明
修改
结合以上知识,我们直接从\(i\)开始循环找到它的每个父辈,加上要修改的值即可
void add(int a, int b)
{
for(; a <= n; a += lowbit(a))
tree[a] += b;
}
查询求和
大概意思是将\(C[i]\)分割成小的儿子节点
我也不知道怎么解释,看动图吧
LL query(int a)
{
LL sum = 0;
while(a >= 1)
{
sum += tree[a]; // 求和
a -= lowbit(a); // 分割
}
return sum;
}
构造
遍历整个待构造的数组,把每个点都加入树状数组即可
void init()
{
for(int i = 1; i <= n; i++) // 遍历
{
LL a;
cin >> a;
add(i, a); // 加入树状数组
}
}
对了,记得开long long
Talk is cheap, show me the code!
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
int n, m;
LL tree[1000010];
inline LL read() // 快读,不然不保证能稳定过
{
LL s = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
return f * s;
}
int lowbit(int x)
{
return x & -x;
}
LL query(int a) // 查询求和
{
LL sum = 0;
while(a >= 1)
{
sum += tree[a];
a -= lowbit(a);
}
return sum;
}
void add(int a, int b) // 加入树状数组
{
for(; a <= n; a += lowbit(a))
tree[a] += b;
}
void init()
{
for(int i = 1; i <= n; i++)
{
LL a;
cin >> a;
add(i, a);
}
}
int main()
{
n = read();
m = read();
init();
for(int i = 1; i <= m; i ++)
{
int op;
op = read();
LL a, b;
a = read(), b = read(); // 读入
if(op == 1) // 哪个操作呢
add(a, b); // 加边加边加边
else
printf("%lld\n", query(b) - query(a - 1)); // 1~b的区间和 - 1~a-1的区间和即为 a~b的区间和
// 然后并查集查询(大雾)
}
return 0;
}
蒟蒻的题解难免有误,恳请dalao指出!