线段树和树状数组,是两个十分相似的数据结构。他们能使对一个区间的数修改以及查询的速度提升许多。两个结构本质相同,各有优缺点,今天我们来从单点修改,单点查询,区间修改,区间查询。
个人认为树状数组比较简单,就先讲一下树状数组吧(luogu模板题)
https://www.luogu.org/problemnew/show/P3374 https://www.luogu.org/problemnew/show/P3368
树状数组
差不多长这样的
也长这个样子
c1=a1;
c2=a1+a2;
c3=a3;
c4=a1+a2+a3+a4;
然后悄咪咪的把它变成二进制
c0001=a0001
c0010=a0001+a0010
c0011=a0011
c0100=a0001+a0010+a0011+a0100
顿时发现了规律有没有
先来简单的介绍一下lowbit函数,他是用来链接树状数组关系的函数,说的通俗点,它每个节点存储的是他的子子孙孙的和
int lowbit {returnx&(-x);}
那么这个lowbit是什么意思呢
这个算出来的是他的最后一位非零数的位所对应的值
比如
1,3,5,7这些奇数,他们的lowbit值就是1,同样,对应到图上,我们会发现它的位置都在第一排,也就是说,他们是没有儿子的,它存储的和就是他自己,
2,6,10这些2的倍数,lowbit是2
4 8 12这些lowbit是4
这些x加上他的lowbit值就变成了他的爸爸,而减去就变成了他的弟弟(结合图观察)
lowbit (1)+1=2
lowbit (2)+2=4
lowbit (3)+3=4
lowbit (4)+4=8
lowbit (5)+5=6
.......
修改
这是我们对树状数组进行修改时的重要依据,每次先自我修改,然后向上找爸爸,对爸爸进行修改一直向上
那么就顺便附一波代码
void add(int x,int k) { //给编号为x的点加上k
while(x<=n){ //n为数组的一个上线,通常因题而异
tree[x]+=k;
x=x+lowbit(x); //找爸爸
}
}
建树时,树状数组比较简单,不像线段树那么麻烦
for(int i=1;i<=n;i++) {scanf("%d",&a);add(i,a)} //在i的位置附上a就相当于加a
顺便就来说一下修改
单点修改可以直接使用
scanf("%d%d",&a,&k);add(a,k);
区间修改时需要用一点差分的思想
如果将x到y区间加上一个k,那就是从x到n都加上一个k,再从y+1到n加上一个-k
那么是这么用的
scanf("%d%d%d",&x,&y,&k);
add(x,k),add(y+1,-k);
整个的修改就是这样,大概就是一直找爸爸的过程
求值
修改实在一直找爸爸,而求值是一直找弟弟,如果没有弟弟,它爸爸的弟弟也是他的弟弟(这个关系好复杂 )
首先先来看一下每个数减去他的lowbit值
1-lowbit (1)=0
2-lowbit (2)=0
3-lowbit (3)=2
4-lowbit (4)=0
5-lowbit (5)=4
6-lowbit (6)=4
7-lowbit (7)=6
8 ......
为了让这个关系看的清楚点,再把图片放一遍(hiahiahia)
经过对比,思考一下这个找弟弟的过程
那么他是怎么查询呢
就拿7来模拟一下
tree[7]=a[7]; 7-lowbit (7)=6;
tree[6]=a[5]+a[6]; 6-lowbit (6)=4;
tree[4]=a[1]+a[2]+a[3]+a[4] 4-lowbit (4)=0;
根据这个,我们可以推出他的求值的代码
int query(int x) { //x及以前所有的和,复杂度logn
int sum=0;
while(x!=0){
sum+=tree[x];
x-=lowbit(x);
}
return sum;
}
所以单点求值就是简单的直接printf(“%d”,query(x));
就行了
区间求值后面的减去前面的,还是比较好理解的
printf("%d",query(r)-query(l-1));
所以树状数组就是这个样子,他代码非常好打,就是在理解上有一定的难度,精髓是他的lowbit函数
附luoguAC代码
p3374
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#define FOR(i,n,m) for(int i=n;i<=m;i++)
#define FR(j,n,m) for(int i=n;i>=m;i--)
#define MAX 100
#define N 500020
#define mo 1000
#define ll long long
#define ull unsigned long long
using namespace std;
int n,m,tree[N];
int lowbit(int x) {return x&(-x);}
void add(int x,int y) {
while(x<=n){
tree[x]+=y;
x=x+lowbit(x);
}
}
int query(int x){
int ans=0;
while(x!=0){
ans+=tree[x];
x-=lowbit(x);
}
return ans;
}
int main(){
int a;
scanf("%d%d",&n,&m);
FOR(i,1,n) scanf("%d",&a), add(i,a);
FOR(i,1,m){
int k,x,y;
cin>>k>>x>>y;
if(k==1) add(x,y);
if(k==2) cout<<query(y)-query(x-1)<<endl;
}
return 0;
}
p3368
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
int n,m,tree[N];
int lowbit(int x) {return x&(-x);}
void add(int x,int y) {
while(x<=n){
tree[x]+=y;
x=x+lowbit(x);
}
}
int query(int x){
int ans=0;
while(x!=0){
ans+=tree[x];
x-=lowbit(x);
}
return ans;
}
int main() {
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++) {
scanf("%d",&t);
add(i,t),add(i+1,-t);
}
for(int i=1; i<=m; i++) {
scanf("%d",&t);
if(t==1) {
scanf("%d%d%d",&x,&y,&z);
add(x,z),add(y+1,-z);
} else if(t==2) {
scanf("%d",&x);
printf("%d\n",query(x));
}
}
return 0;
}