树状数组
Posted whx1003
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了树状数组相关的知识,希望对你有一定的参考价值。
题目 树状数组模板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)) 的。
树状数组(又称二叉索引树,Fenwick树)是一类最基础的树形数据结构。它支持 (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 ightarrow 8(0110+0010=1000));查询一个数时,下一个要修改的目标位置就是当前位置减去当前位置的最后一位,如 (5 ightarrow 4(0101-0001=0100))。那么现在问题转化为了如何 (O(1)) 求出某个数二进制最后一位,那便是lowbit函数:
int lowbit(int x)
{
return x & (-x);
}
这个函数可以以 (O(1)) 的优秀复杂度求出某个数二进制的最后一位(比如 (3(0011) ightarrow 1(0001),6(0110) ightarrow 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
", 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
", a[x] + Query(x));
}
}
以上是关于树状数组的主要内容,如果未能解决你的问题,请参考以下文章