树状数组和线段树
树状数组与线段树
对比
1.线段树组的适用范围包含树状数组的适用范围,即树状数组可以解的题线段树可以解决,反之则不然。
2.树状数组也有优势:代码短,运行效率高
3.两种结构下标都是从1开始
树状数组 O(logn)
可以解决的问题:
- 使某个位置上的数加上一个数 单点修改
- 求某一个点的前缀和 区间查询
本身树状数组只支持上述两种操作,但是结合差分的思想可以完成:单点查询,区间修改等
--如果只有查询那就用前缀和,如果还有单点修改那就树状数组。
注意其中:
1.lowbit(x)的定义如图所示。
2.c[x]=(x-lowbit(x),x] 其含义是c[x]计算的是下标从x-lowbit(x)到x左开右闭区间,包右不包左。
3.C、A两个数组下标最好从1开始。
代码模板:
设A数组为原数组,C数组为计算出的树状数组,那么:
单点修改:
原数组a[x]+=v,那么c数组的更新
fori(int i=x;i<=n;i+=lowbit(x)){
c[i]+=v;
}
区间查询:
对于一段区间[1,x]那么c[x]的值如下:
那么要计算[1,x]区间和:
for(int i=x;i>0;i-=lowbit(x)){
res+=c[i];
}
例题:
AcWing 1264. 动态求连续区间和
线段树 O(logn)
可以解决的问题:
-
使某个位置上的数加上一个数 单点修改
-
求某一个区间的xxx 区间查询
注意线段树是只能用来计算前缀和,但是线段树可不只能动态计算前缀和
如果想完成 区间修改、单点查询等操作,可能设计线段树的懒标记操作,由于难度过大,蓝桥杯等级的比赛基本不会涉及,因此目前阶段只考虑单调修改和区间查询。
核心函数
由于结果和树状数组不同,因此要实现单点修改和区间查询操作,线段树主要设计4个核心函数。
下面的伪代码是以计算区间最大值为模板的,需要计算其他值需要相应的修改
①pushup:用子节点信息更新当前结点信息
void pushup(int u){
tr[u].max=Math.max(tr[u<<1].max+tr[u<<1|1].sum);
}
//u代表子节点的下标(两个树的下标都从1开始的),且线段树的四种核心函数都带参数u
//线段树里面存放的是自定义的结构体,如果是最小值里面有三个参数:l、r、max,其中l,r值得是原序列的下标l,r
//u<<1等价于u/2舍尾,即左二子;u<<1|1等价于u/2+1,即右儿子
②build:在一段区间上初始化线段树
void build(int u,int l,int r){
if(l==r){
//到了最底部的结点了
tr[u]={l,r,w[r]};//w就是原数组
}else{
tr[u]={l,r};
int mid=l+r >>1;
//还没到最底部结点,于是先建立下面的结点,最后在pushup(u)算出此结点的值
build(u<<1,l,mid);build(u<<1|1,mid+1,r);//从这就可以看出来构造线段树的时候mid属于左边,mid+1属于右边
pushup(u);
}
}
③modify:修改
void modify(int u,int x,int v){
if(tr[u].l==tr[u].r){
tr[u].max=v;
}else{
int mid=tr[u].l+tr[u].r;
//修改是左右选合适的一个走,build是左右各走一遍。
if (x <= mid) modify(u << 1, x, v);
else modify(u << 1 | 1, x, v);
pushup(u);
}
}
④query:查询
void query(int u,int l,int r){
//如果查询的区间包含某整个区间,就直接返回值
if(l<=tr[u].l && r>=tr[u].r){
return tr[u].max;
}
//注意mid是tr[u].l+tr[u].r>>1即取树的中点,而不是l+r>>1
int mid=tr[u].l+tr[u].r>>1;
int max = Integer.MIN_VALUE;
//query是左右可以走哪一个就走哪一个
if (l <= mid) max =Math.max(max, query(u << 1, l, r));
if (r >= mid+1) max=Math.max(max, query(u << 1 | 1, l, r));
return max;
}
⑤pushdown:带懒标记的线段树设计,不讲解