1.树状数组简介
树状数组是一个很奇特的树,它的节点会比线段树少一些,也能表示一个数组。
树状数组是一种类似于前缀和的数据结构,但是前缀和的修改操作是 O(n) 的,查询是 O(1) 的 。所以就有了树状数组这个数据结构,它的两种操作被中和了,都是 O(logn)的。
线段树也能实现树状数组的功能,但是:相比线段树,树状数组更好写,而且代码很短。
2.基本概念
树状数组是结合了二进制的一种数据结构,它利用二进制来划分每一个节点所表示的前缀和。
树状数组的规律如下:
·C1=a1
·C2=a1+a2
·C3=a3
·C4=a1+a2+a3+a4
·C5=a5
·C6=a5+a6
·C7=a7
·C8=a1+a2+a3+a4+a5+a6+a7+a8
如果这样很难观察的话,把他们改为二进制:
·C0001=a0001
·C0010=a0001+a0010
·C0011=a0011
·C0100=a0001+a0010+a0011+a0100
3.单点修改
首先引入一个函数lowbit,lowbit函数可以计算出非负整数n在二进制下最低位1及其后面的0构成的数值,即lowbit(n)=n&(-n).
比如lowbit(5)=lowbit(0101(二进制))=0001(二进制)
而如果改变ax的值,就要加上自己的lowbit,一直加到n。
代码:
void add(int x,int k)
{
while(x<=n)
{
tree[x]+=k;//第x个数加上k
x+=lowbit(x);
}
return;
}
4.区间查询
就是前缀和,比如查询[x,y]的和,那么就将[1,y]的和 - [1,x-1]的和。
从1到y的和求法是,将y转为2进制,然后一直减去lowbit(y),一直到0
int sum(int x)
{
int ans=0;
while(x!=0)
{
ans+=tree[x];
x-=lowbit(x);
}
return ans;
}
5.区间修改
如果将[x,y]加上一个k,那就是[x,n]都加上一个k,再从[y+1,n]加上一个-k
void add(int x,int k)
{
while(x<=n)
{
tree[x]+=k;
x+=lowbit(x);
}
}
6.代码
树状数组1
#include<iostream>
using namespace std;
const int N=1e6+10;
int a[N];
int n,m;
int lowbit(int x)
{
return x&-x;
}
void add(int x,int k)//单点修改
{
while(x<=n)
{
a[x]+=k;
x+=lowbit(x);
}
return;
}
int sum(int x)
{
int ans=0;
while(x!=0)
{
ans+=a[x];
x-=lowbit(x);
}
return ans;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int w;
scanf("%d",&w);
add(i,w);
}
while(m--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
if(a==1) add(b,c);
if(a==2) printf("%d\n",sum(c)-sum(b-1));//[1.c]的和减去[1,b-1]的和
}
return 0;
}