本蒟蒻第一次发博客,希望支持。
树状数组
概念
树状数组 (Binary Indexed Tree(BIT)也称作(Fenwick Tree) 是一个区间查询和单点修改复杂度都为log(n)的数据结构。主要用于查询任意两点之间的所有元素之和。
例题
【模板】树状数组 1(洛谷)
树状数组 1 :单点修改,区间查询(Liuser’s OJ)
题目描述
如题,已知一个数列,你需要进行下面两种操作:
- 将某一个数加上 x
- 求出某区间每一个数的和
输入格式
第一行包含两个正整数 n,m ,分别表示该数列数字的个数和操作的总个数。
第二行包含 n 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。
接下来 m 行每行包含 3 个整数,表示一个操作,具体如下:
- 含义:将第 x 个数加上 k
1 x k
- 含义:输出区间 [x,y] 内每个数的和
2 x y
输出格式
输出包含若干行整数,即为所有操作 2 的结果。
样例
样例1输入
5 5
1 5 4 2 3
1 1 3
2 2 5
1 3 -1
1 4 2
2 1 4
样例1输出
14
16
数据范围与提示
对于100%的数据,1≤n,m≤5×10^5。
分析
这道题的 常见做法:
for(long long i=x;i<=y;i++)
{
ans+=a[i];
}
//用for循环从区间x到y依次求和,时间复杂度:O(n) 。
缺点:当数据规模极大的时候,将会变得效率低下,容易超时。
这时,我们想到了 前缀和:
//原数组为A[i],再定义一个数组B[i],i≤n+5。
B[1]=A[1];
B[2]=A[1]+A[2];
B[3]=A[1]+A[2]+A[3];
……
sum(A[L]+A[L+1]+……+A[R-1]+A[R]) = B[R]-B[L-1]
优点:输入原数组A时,预处理生成B数组,求和时只需一步相减即可。
缺点:若原数组元素A[i] 进行修改后,B[i]和B[i]以后的元素都得改变,那么修改的时间复杂度为O(n),所以时间复杂度相当于没有变,还是O(n)。
这时,怎么办呢?
我们就要使用 树状数组:
首先,我们要生成树状数组,就如上图生成。
那么这又是怎么生成的呢?
就是通过 Lowbit:
Lowbit(i) 的意思是将 i 转化成二进制数之后,只保留最低位的1及其后面的0,截断前面的内容,然后再转成十进制数,这个数也是树状数组中i号位的子叶个数。
long long Lowbit(long long x)
{
return x&-x;
}
//原数为i(十进制),先将原数转化成二进制之后,在与原数相反数的二进制按位与,答案就是lowbit(i)的结果
对原数组A[i]进行Update(更新)操作
void Update(long long x,long long y)
{
for(long long i=x;i<=n;i+=Lowbit(i))
{
BIT[i]+=y;
}
return ;
}
//更新BIT[i]
求Sum(前缀和)操作
long long Sum(long long x)
{
long long ans=0;
for(long long i=x;i;i-=Lowbit(i))
{
ans+=BIT[i];
}
return ans;
}
//求和
代码+注释
#include<bits/stdc++.h>
using namespace std;
long long a[1000005],BIT[1000005],n,q,s,l,r;
//定义全局数组
long long Lowbit(long long x)
{
return x&-x;
}
//原数为i(十进制),先将原数转化成二进制之后,在与原数相反数的二进制按位与,答案就是lowbit(i)的结果
void Update(long long x,long long y)
{
for(long long i=x;i<=n;i+=Lowbit(i))
{
BIT[i]+=y;
}
return ;
}
//更新BIT[i]
long long Sum(long long x)
{
long long ans=0;
for(long long i=x;i;i-=Lowbit(i))
{
ans+=BIT[i];
}
return ans;
}
//求和
int main()
{
scanf("%lld%lld",&n,&q);
for(long long i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
Update(i,a[i]);
}
//输入时预处理,构造BIT[i]
for(long long i=1;i<=q;i++)
{
scanf("%lld%lld%lld",&s,&l,&r);
if(s==1)
{
Update(l,r);
}
else
{
printf("%lld\n",Sum(r)-Sum(l-1));
//输出区间[L,R]的和
}
}
return 0;
}