一、树状数组的概念
树状数组是利用了数的二进制特征进行检索的一种树状结构。关于树状数组的结构图可以看下这篇文章,这位博主的结构图很详细(树状数组结构图)那么树状数组我们是用来干什么的呢?其实它和前缀和与差分也是也是一样的,也是用来求解关于区间问题的,具体我们可以看看后面的代码展现来感受一下就知道了。
我们定义一个树状数组tree[],原始数组a[]
由图的结构我们可以知道tree[1]=a1
tree[2]=tree[1]+a2
tree[3]=a3
tree[4]=tree[2]+tree[3]+a4
.....
tree[8]=tree[4]+tree[6]+tree[7]+a8
我们会发现一个问题,该如何求出tree呢?这就需要在每次的二进制后面加上一,找到下一个数了。这就需要用到lowbit(x)=x&(-x)来找到二进制中的最后一个一了。
如:
x x二进制 lowbit(x) tree
1 1 1 tree[1]=a1
2 10 2 tree[2]=a1+a2
3 11 1 tree[3]=a3
4 100 4 tree[4]=a4+a3+a2+a1
5 101 1 tree[5]=a5
6 110 2 tree[6]=a6+a5
相信规律很明显我就不细说了吧。
二、树状数组的代码实现
(1)单点修改+区间查询
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x)((x)&-(x))
const int N=1e3+10;
int a[N];
int tree[N];
int n;
void up(int x,int d)
{
while(x<=N){
tree[x]+=d;
x+=lowbit(x);
}
}
int sum(int x)
{
int ans=0;
while(x>0){
ans+=tree[x];
x-=lowbit(x);
}
return ans;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
up(i,a[i]);
}
cout<<sum(6)-sum(3)<<endl;//查看区间[4,6]的和
//现在给a[5]+100
up(5,100);
cout<<sum(6)-sum(3)<<endl;
return 0;
}
(2)区间修改+单点查询
区间修改用到了前面我们学到的拆分知识,修改区间[L,R]的值,用差分,差分数组的变化时d[l]++,
d[r+1]--;这里同样也是,具体的我们看下代码。
注意点是什么呢,与前面的(1)相比较up的第二个参数第一个是a[i],得出来的sum就是a[i]的前缀和,而在(2)点中,up接受的第二个参数是a[i]-a[i-1],那么sum[i]计算的就是a[i],相当于sum[i]是tree[i]的前缀和,也就是说,第二个参数接受的是什么,sum[i]计算得到的就是第二个参数相加累计得到的。
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x)((x)&-(x))
const int N=1e3+10;
int n,m;
int a[N];
int tree[N];
void up(int x,int d)
{
while(x<=N){
tree[x]+=d;
x+=lowbit(x);
}
}
int sum(int x)
{
int ans=0;
while(x>0){
ans+=tree[x];
x-=lowbit(x);
}
return ans;
}
int main()
{
cin>>n>>m;
int old=0;
for(int i=1;i<=n;i++){
cin>>a[i];
up(i,a[i]-old);
old=a[i];
}
while(m--){
int l,r,q;
cin>>l>>r>>q;
up(l,q);
up(r+1,-q);
}
//输出a中的所有元素
for(int i=1;i<=n;i++){
cout<<sum(i)<<" ";
}
return 0;
}
/*
5 3
1 2 3 4 5
1 2 3
1 1 5
3 5 1
*/
(3)区间修改+区间查询
区间查询与区间查询我们就需要两个树状数组了。
原因是什么呢,看下推理公式就知道了。
a1+a2+a3+....+ak
=d1+(d1+d2)+(d1+d2+d3)+...+(d1+d2+d3+...+dk)
=k*d1+(k-1)*d2+(k-2)*d3+...+dk
=k(d1+d2+...+dk)-(d2+2*d3+...+(k-1)*dk)
=d*(d1+...+dk)-(i-1)*di(i从1到k)
那么我们创建的两个树状数组一个实现的是d[i],一个实现的是(i-1)*di
代码展现:
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x)((x)&-(x))
const int N=1e3+10;
int a[N];
int tree1[N],tree2[N];
int n,m;
void up1(int x,int d)
{
while(x<=N){
tree1[x]+=d;
x+=lowbit(x);
}
}
void up2(int x,int d)
{
while(x<=N){
tree2[x]+=d;
x+=lowbit(x);
}
}
int sum1(int x)
{
int ans=0;
while(x>0){
ans+=tree1[x];
x-=lowbit(x);
}
return ans;
}
int sum2(int x)
{
int ans=0;
while(x>0){
ans+=tree2[x];
x-=lowbit(x);
}
return ans;
}
int main()
{
cin>>n>>m;
int old=0;
for(int i=1;i<=n;i++){
cin>>a[i];
up1(i,a[i]-old);
up2(i,(i-1)*(a[i]-old));
old=a[i];
}
while(m--){
int l,r,q;
cin>>l>>r>>q;
up1(l,q);
up2(l,(l-1)*q);
up1(r+1,-q);
up2(r+1,-q*r);
}
//查看[3,5]
cout<<5*sum1(5)-sum2(5)-((3-1)*sum1(3-1)-sum2(3-1));
return 0;
}
/*
7 3
1 2 3 4 5 6 7
1 4 2
3 6 6
1 3 1
*/