线段树学习笔记
0.什么是线段树
线段树有着比分块小的时间复杂度,也有着比树状数组大的通用性,有着比平衡树更小的常数和代码难度,实在是一种非常牛逼的东西。
1.线段树入门
我们要了解线段树,首先得明白它长什么样。
此处放一张图:
如图,一个节点代表一个区间,从中点切开这个区间,就成为了左右区间(不一定严谨,听着吧)。
由于每切一次,区间都减少一半,因此树高为\(\log n\)级别。
叶节点的值直接由序列得出,其它节点有左右节点合并得出。
为方便,将\(p\)的左右节点分别设为\(p\times2\)和\(p\times2+1\)(可以证明这样节点不会重复,只是牺牲了一点空间)。
词穷了,直接开始上代码吧。
//以下为加减,求和操作
void pushup(int p){
val[p]=val[p<<1]+val[p<<1|1];
}
void build(int p,int l,int r){//建树
if(l==r){
val[p]=v[l];//v[l]为序列第l项
}
int mid=(l+r>>1);
build(p<<1,l,mid);build(p<<1|1,mid+1,r);
pushup(p);//将左右节点的信息合并
}
学会了建树,接下来就应该是操作了。
操作1:单点查询
简单到不知道如何解释了,直接上代码。
int query(int p,int l,int r,int pos){//pos为查询位置
if(l==r)return val[p];
int mid=(l+r>>1);
if(pos<=mid)return query(p<<1,l,mid,pos);
else return query(p<<1|1,mid+1,r,pos);
}
操作2:单点修改
自上而下到叶节点进行修改,再自下而上更新
void modify(int p,int l,int r,int pos,int x){
if(l==r){
val[p]+=x;
return;
}
int mid=(l+r>>1);
if(pos<=mid)modify(p<<1,l,mid,pos);
else modify(p<<1,mid+1,r,pos);
}
以上操作暴力都可以做到,现在就要介绍暴力做不到的:区间操作。
首先上简单点的:区间查询。
我们想一想,维护非叶节点有什么用?进行区间操作。
先上代码:
void query(int p,int l,int r,int L,int R){
if(L<=l&&r<=R)return val[p];//当前区间被完全包含,直接返回
int mid=(l+r>>1),ans=0;
if(L<=mid)ans+=query(p<<1,l,mid,L,R);//左儿子与询问区间有交集
if(R>mid)ans+=query(p<<1|1,mid+1,r,L,R);//右儿子与询问区间有交集
}
易证,信息肯定是不重不漏的。
下面进行时间复杂度证明:
只有包含端点的区间才能一直递归下去。
未包含端点有两种情况:完全在询问区间内,完全在询问区间外,这些都是直接返回。
而端点只有两个,树高为\(\log n\)级别,因此查询复杂度也为\(\log n\)
接下来是区间修改。
询问可以不到叶子,因为只要获取信息。但修改不行,因为修改要将信息更新。
好像思路进入了僵局。
但线段树有一个强大的功能:懒标记。
有一个标记,代表此节点已被更新,但子节点未被更新。
如果区间被修改区间完全包含,打一个标记,并更新它的信息。
如果以后需要对此区间的儿子进行操作,就将标记下传,更新儿子节点的信息,将儿子节点打上标记,并将此节点的标记去除。
标记下传复杂度理论为\(O(1)\),因此只是常数大了那么一点(不是亿点)。
void pushdown(int p,int l,int r){
if(tag[p]){
int mid=(l+r>>1);
val[p<<1]+=(mid-l+1)*tag[p];
val[p<<1|1]+=(r-mid)*tag[p];
tag[p<<1]+=tag[p];tag[p<<1|1]+=tag[p];
tag[p]=0;
}
}
void modify(int p,int l,int r,int L,int R,int x){
if(L<=l&&r<=R){
val[p]+=(r-l+1)*x;tag[p]+=x;return;
}
pushdown(p,l,r);int mid=(l+r>>1);
if(L<=mid)modify(lc,l,mid,L,R,x);
if(R>mid)modify(rc,mid+1,r,L,R,x);
}
P3372代码如下:
#include<bits/stdc++.h>
using namespace std;
int n,m,a[100001];
struct tree{
int l,r;
long long sum,add;
}tree[400001];
void label(int p){
if(tree[p].add){
tree[p*2].sum+=(tree[p*2].r-tree[p*2].l+1)*tree[p].add;
tree[p*2+1].sum+=(tree[p*2+1].r-tree[p*2+1].l+1)*tree[p].add;
tree[p*2].add+=tree[p].add;
tree[p*2+1].add+=tree[p].add;
tree[p].add=0;
}
}
void bulid(int l,int r,int p){
tree[p].l=l,tree[p].r=r;
if(l==r){
tree[p].sum=a[l];
return;
}
int mid=(l+r)/2;
bulid(l,mid,p*2);
bulid(mid+1,r,p*2+1);
tree[p].sum=tree[p*2].sum+tree[p*2+1].sum;
}
void change(int l,int r,int p,int num){
if(tree[p].l>=l&&tree[p].r<=r){
tree[p].sum+=1ll*num*(tree[p].r-tree[p].l+1);
tree[p].add+=num;
return;
}
label(p);
int mid=(tree[p].l+tree[p].r)/2;
if(l<=mid)change(l,r,p*2,num);
if(r>mid)change(l,r,p*2+1,num);
tree[p].sum=tree[p*2].sum+tree[p*2+1].sum;
}
long long ask(int l,int r,int p){
long long ans=0;
if(tree[p].l>=l&&tree[p].r<=r){
return tree[p].sum;
}
label(p);
int mid=(tree[p].l+tree[p].r)/2;
if(l<=mid)ans+=ask(l,r,p*2);
if(r>mid)ans+=ask(l,r,p*2+1);
return ans;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",a+i);
}
bulid(1,n,1);
while(m--){
int op,l,r;
scanf("%d%d%d",&op,&l,&r);
if(op==1){
int x;
scanf("%d",&x);
change(l,r,1,x);
}
else printf("%lld\n",ask(l,r,1));
}
return 0;
}
其它操作例如区间最值,其实是大同小异的。
原文地址:https://www.cnblogs.com/andy-lin102/p/16269642.html
- 【Qt】]Qt5中文乱码
- 防止连接Mysql超时,JDBC探活配置
- 剑指offer——面试题10输入一个十进制整数,统计其中二进制1的个数
- 剑指offer——面试题9计算斐波纳切第n个数
- 剑指 offer——面试题8求旋转数组的最小值
- MYSQL INNODB表压缩
- 剑指offer——年龄排序问题
- Mysql Group Replication介绍
- 剑指offer——快速排序
- 架构高性能网站秘笈(四)——反向代理缓存
- 架构高性能网站秘笈(一)——了解衡量网站性能的指标
- MYSQL5.6&5.7编译安装
- 架构高性能网站秘笈(三)——浏览器缓存
- 剑指 offer代码解析——面试题39判断平衡二叉树(高效方法)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法