树状数组
题目 树状数组模板1 题目大意:给定一个序列,要求支持两种操作:1.将某个数加上 \(x\),2.查询区间某部分的和。
题目 树状数组模板2 题目大意:给定一个序列,要求支持两种操作:1.将区间内的每个数加上 \(x\),2.查询某个数。
分析 由于第一题与第二题实际上是等价的(稍后会说明),我们以下仅讨论第一题。我们先以最简单的数组来考虑,显然操作1是 \(O(1)\),操作2是 \(O(n)\) 的,总体复杂度 \(O(nm)\),很明显会超时。这时我们就会想到用前缀和来优化操作2,这样的话操作2是 \(O(1)\) 的,但是操作1是 \(O(n)\) 的,总体复杂度仍为 \(O(nm)\),会超时。有没有一种数据结构,可以使操作1和操作2都是 \(O(1)\) 的呢?仅目前来看是不存在的。于是我们退而求其次寻找一种数据结构,使操作1和操作2都是 \(O(\log n)\) 的。
树状数组(又称二叉索引树,Binary Indexed Tree,BIT)是一类最基础的树形数据结构。它支持 \(O(\log n)\) 进行区间修改单点查询或区间查询单点修改,亦即上述两道题的操作。具体来说,树状数组自身本质上仍是一个数组。我们令 \(a\) 为原数组,\(c\) 为树状数组,假定 \(i\) 二进制最后有 \(j\) 个0,则有 \(c_i=\sum_{k=0}^{2^j}a_{i-k}\)。
(图片来自网络,侵删)
不难发现,现在我们更改与查询的时候只需更改包含着对象的位置就可以了,这样的位置不会超过 \(\log n\) 个(为什么),所以我们只需 \(O(1)\) 从一个位置找到包含原对象的另一个位置就行了。
如果当前要修改 \(1(0001)\),则要修改 \(1(0001),2(0010),4(0100),8(1000)\)。
如果当前要修改 \(5(0101)\),则要修改 \(5(0101),6(0110),8(1000)\)。
如果当前要查询 \(7(0111)\) 的前缀和,则要查询 \(7(0111),6(0110),4(0100)\)。
如果当前要查询 \(5(0101)\) 的前缀和,则要查询 \(5(0101),4(0100)\)。
不难发现,修改一个数时,下一个要修改的目标位置就是当前位置加上当前位置的最后一位,如 \(6\rightarrow 8(0110+0010=1000)\);查询一个数时,下一个要修改的目标位置就是当前位置减去当前位置的最后一位,如 \(5\rightarrow 4(0101-0001=0100)\)。那么现在问题转化为了如何 \(O(1)\) 求出某个数二进制最后一位,那便是lowbit函数:
int lowbit(int x)
{
return x & (-x);
}
这个函数可以以 \(O(1)\) 的优秀复杂度求出某个数二进制的最后一位(比如 \(3(0011)\rightarrow 1(0001),6(0110)\rightarrow 2(0010)\)),证明只需一点点基础的二进制编码,留给读者自证。
那么现在我们就可以愉快地写出第一道题的代码。而对于第二题,不难发现,只要对原数列进行差分,就可以将区间修改转变为单点修改,单点查询转变为区间查询。
代码
第一题:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5E+5 + 5;
int n, m;
int c[maxn];
int Read()
{
int x = 0, op = 1;
char ch = getchar();
while(ch < '0' || ch > '9') {
if(ch == '-') op = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch - '0');
ch = getchar();
}
return x * op;
}
inline int lowbit(int x) { return x & (-x); }
inline void Add(int pos, int x) { while(pos <= n) c[pos] += x, pos += lowbit(pos); }
int Query(int pos)
{
int res = 0;
while(pos) {
res += c[pos];
pos -= lowbit(pos);
}
return res;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i) Add(i, Read());
while(m--) {
int op = Read(), x = Read(), y = Read();
if(op == 1) Add(x,y);
else printf("%d\n", Query(y) - Query(x - 1));
}
}
第二题:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5E+5 + 5;
int n, m;
int a[maxn], c[maxn];
int Read()
{
int x = 0, op = 1;
char ch = getchar();
while(ch < '0' || ch > '9') {
if(ch == '-') op = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch - '0');
ch = getchar();
}
return x * op;
}
inline int lowbit(int x) { return x & (-x); }
inline void Add(int pos, int x) { while(pos <= n) c[pos] += x, pos += lowbit(pos); }
int Query(int pos)
{
int res = 0;
while(pos) {
res += c[pos];
pos -= lowbit(pos);
}
return res;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i) a[i] = Read();
while(m--) {
int op = Read(), x = Read(), y, z;
if(op == 1) y = Read(), z = Read(), Add(x, z), Add(y + 1, -z);
else printf("%d\n", a[x] + Query(x));
}
}