树状数组
树状数组是什么?
不写什么哪年谁发明的了,太无聊,有兴趣的话自行百度即可
树状数组,时间复杂度O(mlogn)明显优于暴力枚举以及前缀和,主要用于单点修改区间查询(当然还有区间修改单点查询),如果一道题中只有区间查询,那么建议使用前缀和维护
树状数组的思想
思想直接理解不好理解,借助数据
a数组下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
数值 | 2 | 5 | 6 | 3 | 2 | 7 | 1 | 4 |
以上是我们要存储的a数组,就是原数据
b数组下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
数值 | 2 | 7 | 6 | 16 | 2 | 9 | 1 | 26 |
以上是操作完的树状数组,探索规律
b[1]=a[1]
b[2]=a[1]+a[2]=b[1]+a[2]
b[3]=a[3]
b[4]=a[1]+a[2]+a[3]+a[4]=b[2]+b[3]+a[4]
b[5]=a[5]
b[6]=a[5]+a[6]=b[5]+a[6]
b[7]=a[7]
b[8]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8]=b[4]+b[6]+b[7]+a[8]
这张图是翻博客上的,博主说是从网上找的,所以也就不标明出处了,很优美的一张图
用树状数组的形式表示出来即为上图(上图指第二张)
也就是说,b数组负责维护一段a数组的区间和
这里引入一个lowbit函数 ,lowbit(a)表示a的二进制格式下最后一个1表示的数
十进制数 | 二进制数 | lowbit值 |
1 | 0001 | 1 |
2 | 0010 | 2 |
3 | 0011 | 1 |
4 | 0100 | 4 |
5 | 0101 | 1 |
6 | 0110 | 2 |
7 | 0111 | 1 |
8 | 1000 | 8 |
… | … | … |
结合之前的图,我们可以发现,当a+lowbit(a)=b时,a就会包括在b维护的这段区间和中,若b+lowbit(b)=c,那么a,b就都包括在c所维护的区间和中,树状数组的各种操作均通过这个性质来进行
接下来直接结合代码实现吧
几种常用函数:
lowbit
int lowbit(int x)//求x的lowbit值
{
return x&-x;//这个不用理解了,直接背过就行
}
单点修改
int modify(int p,int c)//给p加上c
{
for (int i=p;i<=n;i+=lowbit(i))//n是数据数量
b[i]+=c;//修改树状数组的值
}
建立树状数组(其实就是n次单点修改)
for (int i=1;i<=n;i++)
modify(i,a[i]);
区间查询(其实就是求前缀和)
int query(int l)//求1~l的前缀和
{
int ans;
for (int i=l;i>=1;i-=lowbit(i))
ans=ans+b[i];
return ans;
}
附例题 洛谷 3374 【模板】树状数组 1https://www.luogu.org/problemnew/show/P3374
#include<bits/stdc++.h>
#define maxn 500050
using namespace std;
int n,m;
int a[maxn],b[maxn];
int lowbit(int x)
{
return x&-x;
}
int modify(int x,int y)
{
for (int i=x;i<=n;i+=lowbit(i))
b[i]+=y;
}
int query(int x)
{
int ans=0;
for (int i=x;i>=1;i-=lowbit(i))
ans+=b[i];
return ans;
}
int main()
{
cin>>n>>m;
for (int i=1;i<=n;i++)
scanf("%d",&a[i]);
for (int i=1;i<=n;i++)
modify(i,a[i]);
for (int i=1;i<=m;i++)
{
int p;
scanf("%d",&p);
if (p==1)
{
int c,k;
scanf("%d%d",&c,&k);
modify(c,k);
}
if (p==2)
{
int l,r;
scanf("%d%d",&l,&r);
printf("%d\n",query(r)-query(l-1));
}
}
return 0;
}