用于提高区间内的数据修改和查询速度。
应用无非有四类:单点修改,单点查询,区间修改,区间查询。下面将通过两个实例分别用树状数组和线段树来解决理解二者的基本应用及区别
问题1:
包括两种操作:将某一个数加上x(单点更新),求某一区间上每一个数的和(区间查询)
输入:区间数字个数n, 操作数量m, n个原始数值
m个操作(1 x k: x加上k) (2 x y: 查询x到y的和)
问题2:
包括两种操作:将某一区间每一个数加上x(区间更新),求某一个数值(单点查询)
输入:区间数字个数n, 操作数量m, n个原始数值
m个操作(1 x y k: x到y加上k) (2 x y: 查询x)
树状数组--问题1:
#include<bits/stdc++.h>
using namespace std;
const int N = 500000+7;
int n,m;
int a[N];
#define lowbit(x) (x&(-x))
void update(int index,int val)
{
for(int i=index;i<=n;i+=lowbit(i)) a[i]+=val;
}
int get_sum(int index)
{
int ans=0;
for(int i=index;i;i-=lowbit(i)) ans+=a[i];
return ans;
}
int main()
{
cin>>n>>m;
int q,k,y;
for(int i=1;i<=n;i++)
{
cin>>k;
update(i,k);
}
while(m--)
{
cin>>q>>k>>y;
if(q==1)
{
update(k,y);
}else
{
cout<<get_sum(y)-get_sum(k-1)<<endl;
}
}
}
树状数组主要函数:
lowbit找到最低位1 add实现单点更新 get_sum求得区间1到index的和
注意:get_sum求x到y区间和时用get_sum(y)-get_sum(x-1) 别忘了-1
实现了单点更新,区间查询
树状数组--问题2:
#include<bits/stdc++.h>
using namespace std;
const int N = 500000+7;
int a[N];
#define lowbit(x) (x&(-x))
int n,m,k,x,y,q;
void update(int index,int val)
{
for(int i=index;i<=n;i+=lowbit(i)) a[i]+=val;
}
int get_sum(int index)
{
int ans=0;
for(int i=index;i;i-=lowbit(i)) ans+=a[i];
return ans;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>k;
update(i,k);
update(i+1,-k);
}
while(m--)
{
cin>>q;
if(q==1)
{
cin>>x>>y>>k;
update(x,k);
update(y+1,-k);
}
else
{
cin>>k;
cout<<get_sum(k)<<endl;
}
}
}
树状数组主要函数:
lowbit找到最低位1 add实现单点更新 get_sum求得区间1到index的和
与单点更新区间查询所用函数完全相同
实现区间更新时将原数列视作差分数组,区间x到y全部+k需要add(x,k)和add(y+1,-k)两步实现
*需要注意的是,若视原数列为差分数组,则在初始更新时每一步都需要add两次,为了解决这一问题,可另开一个数组存储原始数列,树状数组只存储变化量,输出时只需要将变化量加在原始数列即可
单点查询其实就是区间长度为1的查询。
综上
更新操作:更新时需要根据更新方式选择不同的方式存储原始数列,单点更新则选普通存储,区间更新则用差分存储。
若为单点更新,查询时区间和单点没有区别,但如果只用到单点查询的化使用树状数组就没有意义了。
若为区间更新,则get_sum()的结果只能是某个数的值,也就是只能实现单点查询。
由此:一般树状数组的单点查询搭配区间更新,区间查询搭配单点更新
线段树--问题1:
#include<bits/stdc++.h>
using namespace std;
int n,m;
int ans;
int he=0;
int input[500010];
struct node{
int left,right;
int num;
}tree[2000010];
void build(int left,int right,int index)
{
he++;
tree[index].left=left;
tree[index].right=right;
if(left==right) return;
int mid=(left+right)/2;
build(left,mid,index*2);
build(mid+1,right,index*2+1);
}
int add(int index)
{
if(tree[index].left==tree[index].right)
{
tree[index].num=input[tree[index].right];
return tree[index].num;
}
tree[index].num=add(index*2)+add(index*2+1);
return tree[index].num;
}
void my_plus(int index,int dis,int k)
{
tree[index].num+=k;
if(tree[index].left==tree[index].right) return;
if(dis<=tree[index*2].right)
my_plus(index*2,dis,k);
if(dis>=tree[index*2+1].left)
my_plus(index*2+1,dis,k);
}
void search(int index,int l,int r)
{
if(tree[index].left>=l&&tree[index].right<=r)
{
ans+=tree[index].num;
return;
}
if(tree[index*2].right>=l)
search(index*2,l,r);
if(tree[index*2+1].left<=r)
search(index*2+1,l,r);
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
scanf("%d",&input[i]);
build(1,n,1);
add(1);
for(int i=1;i<=m;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
if(a==1)
{
my_plus(1,b,c);
}
if(a==2)
{
ans=0;
search(1,b,c);
printf("%d\n",ans);
}
}
}
线段树主要函数:
void build(int left,int right,int index)用built(1, n, 1)初始化树数组tree的left和right
int add(int index) 单点更新的方法add(1)初始化数组tree的num值
void my_plus(int index,int dis,int k) 单点dis更新+k, index=1所有包含dis的区间都加k
void search(int index,int l,int r) 区间查询,完全包含在l,r内的区间加入后返回
注意:tree开到数列最大值的4倍(MAXN<<2)
实现了单点更新,区间查询
线段树--问题2:
#include<bits/stdc++.h>
using namespace std;
int n,m;
int ans;
int input[50010];
struct node{
int left,right;
int num;
}tree[2000010];
void build(int left,int right,int index)
{
tree[index].num=0;
tree[index].left=left;
tree[index].right=right;
if(left==right) return;
int mid=(right+left)/2;
build(left,mid,index*2);
build(mid+1,right,index*2+1);
}
/*
void add(int index)
{
if(tree[index].left==tree[index].right)
{
tree[index].num=input[tree[index].left];
return tree[index].num;
}
tree[index].num=add(index*2)+add(index*2+1);
return tree[index].num;
}
*/
void pls(int index,int l,int r,int k)
{
if(tree[index].left>=l&&tree[index].right<=r)
{
tree[index].num+=k;
return;
}
if(tree[index*2].right>=l)
pls(index*2,l,r,k);
if(tree[index*2+1].left<=r)
pls(index*2+1,l,r,k);
}
void search(int index,int dis)
{
ans+=tree[index].num;
if(tree[index].left==tree[index].right) return;
if(dis<=tree[index*2].right)
search(index*2,dis);
if(dis>=tree[index*2+1].left)
search(index*2+1,dis);
}
int main()
{
int n,m;
cin>>n>>m;
build(1,n,1);
for(int i=1;i<=n;i++)
scanf("%d",&input[i]);
for(int i=1;i<=m;i++)
{
int a;
scanf("%d",&a);
if(a==1)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
pls(1,x,y,z);
}
if(a==2)
{
ans=0;
int x;
scanf("%d",&x);
search(1,x);
printf("%d\n",ans+input[x]);
}
}
}
线段树主要函数:
void build(int left,int right,int index) 同上
不需要add函数,同树状数组对问题2的解法,tree里存的只是变化量
void pls(int index,int l,int r,int k) 整个包含在l,r内的区间num+k后直接返回,不再往下处理
void search(int index,int dis) 寻找单点,过程中所有经过的区间的num都加到最总ans里
注意:这里某区间的num值是k的话表示次区间内所有值都+k.(实现区间更新)
综上
线段树单点更新,区间查询时用add载入初始值,my_plus更新单点,search有三个参数
区间更新,单点查询时不用add函数,注意tree的num的含义,这也是实现区间快速更新的关键。 search有两个参数,但加上搜索路径上所有num的值。
线段树VS树状数组:
/**时间复杂度**/都是nlogn但树状数组只有在最坏情况下会这样,所以是树状数组略快
/**空间复杂度**/线段树还要开4倍大小的tree结构体数组,完败
/**coding难易**/明显树状数组更好写
但存在总有它的道理,线段树能适用于很多方面,不仅仅是区间、单点的查询修改,还有标记等等,可以用于模拟、DP等等,而且空间经过离散化以后也可以相对压缩,所以适用范围线段树更加广一些。