c++ 线段树
关于线段树
线段数是一种区间树

可以看出:叶子即为输入的数
假设一个节点为 x ,则其左儿子为 2x 右儿子为 2x+1
操作解析
约定
| 变量名 | 意义 |
|---|---|
| input[] | 输入的数 |
| t[] | 线段树 |
| 其中 t[] 是个结构体,包含左边界 l ,右边界 r 和区间和 sum | |
| sum 并不是必须有的,这些维护的值需要根据题目要求增多、减少 |
基本操作
卡常必备
左儿子与右儿子
#define ls rt<<1
#define rs rt<<1|1
push_up
这里只是更新区间和,如有更多操作还需更改
当前区间和 = 左儿子区间和 + 右儿子区间和
inline void push_up(int rt){
t[rt].sum=t[ls].sum+t[rs].sum;
}
build
作用:构建线段树
思路:
- 给当前的 l 和 r 区间赋值
- 判断是否为叶子节点,是就把当前位置的 sum 赋为 input[l] 并返回
- 否则继续构建
void build(int l,int r,int rt){
t[rt].l=l,t[rt].r=r;
if(l==r){
t[rt].sum=input[l];
return;
}
int mid=(l+r)>>1;
build(l,mid,ls);
build(mid+1,r,rs);
push_up(rt);
}
正式开始
单点修改
作用:把位置 p 的值加上 k
当然我们需要维护
如何判断左右子树是否包含 p 呢?

左子树的右边界大于等于 p 就算包含
右子树的左边界小于等于 p 也是包含
思路:
- 先将当前位置的 sum 加上 k
- 如果达到叶子,返回
- 判断左右子树是否包含并继续更新
- push_up
void add(int p,int k,int rt){
t[rt].sum+=k;
if(t[rt].l==t[rt].r) return;
if(p<=t[ls].r) add(p,k,ls);
if(p>=t[rs].l) add(p,k,rs);
push_up(rt);
return;
}
区间修改(加法)
作用:把 [l,r] 区间加上 k
运用了懒标记思想, add 表示当前区间需要加上多少
下传标记
把 add 传到左右子树并更新 sum
sum 显然就要加上区间长度乘 add
inline void down(int rt){
if(t[rt].add){
t[ls].sum+=(t[ls].r-t[ls].l+1)*t[rt].add;
t[rs].sum+=(t[rs].r-t[rs].l+1)*t[rt].add;
t[ls].add+=t[rt].add;
t[rs].add+=t[rt].add;
t[rt].add=0;
}
}
递归修改
思路:
- 如果该区间被完全包含,更新 sum 打上标记并返回
- down
- 判断左右区间是否包含并继续更新
- push_up
void pls(int l,int r,int k,int rt){
if(l<=t[rt].l&&r>=t[rt].r){
t[rt].sum+=k*(t[rt].r-t[rt].l+1);
t[rt].add+=k;
return;
}
down(rt);
if(l<=t[ls].r) pls(l,r,k,ls);
if(r>=t[rs].l) pls(l,r,k,rs);
push_up(rt);
}
单点查询
思路:
- 如果找到该点,返回 sum
- 判断左右区间是否包含并继续查找
long long search(int p,int rt){
if(t[rt].l==p&&t[rt].r==p)
return t[rt].sum;
if(p<=t[ls].r) return search(p,ls);
if(p>=t[rs].l) return search(p,rs);
}
区间查询
思路:
- 如果区间被完全包含,返回 sum
- 判断左右区间是否包含并把查找的值加到 s
- 返回 s
long long query(int l,int r,int rt){
if(l<=t[rt].l&&r>=t[rt].r)
return t[rt].sum;
long long s=0;
if(l<=t[ls].r) s+=query(l,r,ls);
if(r>=t[rs].l) s+=query(l,r,rs);
return s;
}
例题
Warning
- 如果遇到需要区间修改的,查询时一定要下传标记
- 十年 OI 一场空,不开 long long 见祖宗
区改 + 区查
#include<bits/stdc++.h>
#define ls rt<<1
#define rs rt<<1|1
using namespace std;
typedef long long ll;
struct QwQ{
int l,r;
ll sum,add;
}t[2000010];
inline void push_up(int rt){
t[rt].sum=t[ls].sum+t[rs].sum;
}
inline void down(int rt){
if(t[rt].add){
t[ls].sum+=(t[ls].r-t[ls].l+1)*t[rt].add;
t[rs].sum+=(t[rs].r-t[rs].l+1)*t[rt].add;
t[ls].add+=t[rt].add;
t[rs].add+=t[rt].add;
t[rt].add=0;
}
}
int input[500002];
void build(int l,int r,int rt){
t[rt].l=l,t[rt].r=r;
if(l==r){
t[rt].sum=input[l];
return;
}
int mid=l+r>>1;
build(l,mid,ls);
build(mid+1,r,rs);
push_up(rt);
}
void pls(int l,int r,int k,int rt){
if(l<=t[rt].l&&r>=t[rt].r){
t[rt].sum+=k*(t[rt].r-t[rt].l+1);
t[rt].add+=k;
return;
}
down(rt);
if(l<=t[ls].r) pls(l,r,k,ls);
if(r>=t[rs].l) pls(l,r,k,rs);
push_up(rt);
}
ll query(int l,int r,int rt){
if(l<=t[rt].l&&r>=t[rt].r)
return t[rt].sum;
down(rt);
ll s=0;
if(l<=t[ls].r) s+=query(l,r,ls);
if(r>=t[rs].l) s+=query(l,r,rs);
return s;
}
int n,m,opt,x,y,k;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&input[i]);
build(1,n,1);
while(m--){
scanf("%d%d%d",&opt,&x,&y);
if(opt==1){
scanf("%d",&k);
pls(x,y,k,1);
}
else printf("%lld\n",query(x,y,1));
}
}
区改 + 单查
#include<bits/stdc++.h>
#define ls rt<<1
#define rs rt<<1|1
using namespace std;
typedef long long ll;
struct QwQ{
int l,r;
ll sum,add;
}t[2000010];
inline void push_up(int rt){
t[rt].sum=t[ls].sum+t[rs].sum;
}
inline void down(int rt){
if(t[rt].add){
t[ls].sum+=(t[ls].r-t[ls].l+1)*t[rt].add;
t[rs].sum+=(t[rs].r-t[rs].l+1)*t[rt].add;
t[ls].add+=t[rt].add;
t[rs].add+=t[rt].add;
t[rt].add=0;
}
}
int input[500002];
void build(int l,int r,int rt){
t[rt].l=l,t[rt].r=r;
if(l==r){
t[rt].sum=input[l];
return;
}
int mid=l+r>>1;
build(l,mid,ls);
build(mid+1,r,rs);
push_up(rt);
}
void pls(int l,int r,int k,int rt){
if(l<=t[rt].l&&r>=t[rt].r){
t[rt].sum+=k*(t[rt].r-t[rt].l+1);
t[rt].add+=k;
return;
}
down(rt);
if(l<=t[ls].r) pls(l,r,k,ls);
if(r>=t[rs].l) pls(l,r,k,rs);
push_up(rt);
}
ll search(int l,int r,int rt){
if(l<=t[rt].l&&r>=t[rt].r)
return t[rt].sum;
down(rt);
ll s=0;
if(l<=t[ls].r) s+=search(l,r,ls);
if(r>=t[rs].l) s+=search(l,r,rs);
return s;
}
ll search(int p,int rt){
if(t[rt].l==p&&t[rt].r==p)
return t[rt].sum;
down(rt);
if(p<=t[ls].r) return search(p,ls);
if(p>=t[rs].l) return search(p,rs);
}
int n,m,opt,x,y,k;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&input[i]);
build(1,n,1);
while(m--){
scanf("%d%d",&opt,&x);
if(opt==1){
scanf("%d%d",&y,&k);
pls(x,y,k,1);
}
else printf("%lld\n",search(x,1));
}
}
单改 + 区查
#include<bits/stdc++.h>
#define ls rt<<1
#define rs rt<<1|1
using namespace std;
struct QwQ{int l,r,sum;}t[2000010];
inline void push_up(int rt){
t[rt].sum=t[ls].sum+t[rs].sum;
}
int input[500002];
void build(int l,int r,int rt){
t[rt].l=l,t[rt].r=r;
if(l==r){
t[rt].sum=input[l];
return;
}
int mid=l+r>>1;
build(l,mid,ls);
build(mid+1,r,rs);
push_up(rt);
}
void add(int p,int k,int rt){
t[rt].sum+=k;
if(t[rt].l==t[rt].r)
return;
if(p<=t[ls].r) add(p,k,ls);
if(p>=t[rs].l) add(p,k,rs);
push_up(rt);
return;
}
int search(int l,int r,int rt){
if(t[rt].l>=l&&t[rt].r<=r)
return t[rt].sum;
int s=0;
if(t[ls].r>=l) s+=search(l,r,ls);
if(t[rs].l<=r) s+=search(l,r,rs);
return s;
}
int n,m,opt,x,y,k;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&input[i]);
build(1,n,1);
while(m--){
scanf("%d%d%d",&opt,&x,&y);
if(opt==1) add(x,y,1);
else printf("%d\n",search(x,y,1));
}
}
复杂的区间操作
区间乘法
例题
解析
多了一个懒标记 mul ,初值为 1
根据优先级,先乘再加,运算时 mod 不要忘
更新 mul 时 add 也对应乘一下,保证精度
代码
#include<bits/stdc++.h>
#define ls rt<<1
#define rs rt<<1|1
using namespace std;
typedef long long ll;
struct QwQ{
int l,r;
ll sum,add,mul;
}t[2000010];
int input[500002],mod;
inline void push_up(int rt){
t[rt].sum=(t[ls].sum+t[rs].sum)%mod;
}
inline void down(int rt){
t[ls].sum=(t[ls].sum*t[rt].mul+(t[ls].r-t[ls].l+1)*t[rt].add)%mod;
t[rs].sum=(t[rs].sum*t[rt].mul+(t[rs].r-t[rs].l+1)*t[rt].add)%mod;
t[ls].mul=(t[ls].mul*t[rt].mul)%mod;
t[rs].mul=(t[rs].mul*t[rt].mul)%mod;
t[ls].add=(t[ls].add*t[rt].mul+t[rt].add)%mod;
t[rs].add=(t[rs].add*t[rt].mul+t[rt].add)%mod;
t[rt].mul=1,t[rt].add=0;
}
void build(int l,int r,int rt){
t[rt].l=l,t[rt].r=r,t[rt].mul=1;
if(l==r) t[rt].sum=input[l];
else{
int mid=l+r>>1;
build(l,mid,ls);
build(mid+1,r,rs);
push_up(rt);
}
t[rt].sum%=mod;
}
void xMul(int l,int r,int k,int rt){
if(l<=t[rt].l&&r>=t[rt].r){
t[rt].sum=(t[rt].sum*k)%mod;
t[rt].mul=(t[rt].mul*k)%mod;
t[rt].add=(t[rt].add*k)%mod;
return;
}
down(rt);
if(l<=t[ls].r) xMul(l,r,k,ls);
if(r>=t[rs].l) xMul(l,r,k,rs);
push_up(rt);
}
void pls(int l,int r,int k,int rt){
if(l<=t[rt].l&&r>=t[rt].r){
t[rt].sum=(t[rt].sum+k*(t[rt].r-t[rt].l+1))%mod;
t[rt].add=(t[rt].add+k)%mod;
return;
}
down(rt);
if(l<=t[ls].r) pls(l,r,k,ls);
if(r>=t[rs].l) pls(l,r,k,rs);
push_up(rt);
}
ll query(int l,int r,int rt){
if(l<=t[rt].l&&r>=t[rt].r)
return t[rt].sum;
down(rt);
ll s=0;
if(l<=t[ls].r) s+=query(l,r,ls);
if(r>=t[rs].l) s+=query(l,r,rs);
return(s%mod);
}
int n,m,opt,x,y,k;
int main(){
scanf("%d%d%d",&n,&m,&mod);
for(int i=1;i<=n;i++) scanf("%d",&input[i]);
build(1,n,1);
while(m--){
scanf("%d%d%d",&opt,&x,&y);
if(opt==1){
scanf("%d",&k);
xMul(x,y,k,1);
}
else if(opt==2){
scanf("%d",&k);
pls(x,y,k,1);
}
else printf("%lld\n",query(x,y,1));
}
}
区间开方
例题
解析
这题的突破口在于: \(\sqrt{1}=1\)
由于是向下取整,所以最多开方六次就不变了

我们可以省去懒标记,多加一个 fir 表示区间最大值,区间开方时如果 fir 小于等于 1 就无须继续修改了
当修改到达叶子节点,把当前节点的 sum 和 fir 都开个方并返回,因为返回之后上一层会 push_up ,达到修改效果
代码
这题唯一坑点:左区间会比右区间大,需要交换
#include<bits/stdc++.h>
#define ls rt<<1
#define rs rt<<1|1
using namespace std;
typedef long long ll;
struct QwQ{
int l,r;
ll sum,fir;
}t[400010];
inline void push_up(int rt){
t[rt].sum=t[ls].sum+t[rs].sum;
t[rt].fir=max(t[ls].fir,t[rs].fir);
}
ll input[100005];
void build(int l,int r,int rt){
t[rt].l=l,t[rt].r=r;
if(l==r){
t[rt].sum=t[rt].fir=input[l];
return;
}
int mid=l+r>>1;
build(l,mid,ls);
build(mid+1,r,rs);
push_up(rt);
}
void xSqrt(int l,int r,int rt){
if(t[rt].l==t[rt].r){
t[rt].sum=sqrt(t[rt].sum);
t[rt].fir=sqrt(t[rt].fir);
return;
}
if(l<=t[ls].r&&t[ls].fir>1) xSqrt(l,r,ls);
if(r>=t[rs].l&&t[rs].fir>1) xSqrt(l,r,rs);
push_up(rt);
}
ll search(int l,int r,int rt){
if(l<=t[rt].l&&r>=t[rt].r)
return t[rt].sum;
ll s=0;
if(l<=t[ls].r) s+=search(l,r,ls);
if(r>=t[rs].l) s+=search(l,r,rs);
return s;
}
int n,m,opt,x,y;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",&input[i]);
build(1,n,1);
scanf("%d",&m);
while(m--){
scanf("%d%d%d",&opt,&x,&y);
if(x>y) x^=y^=x^=y;
if(opt==0) xSqrt(x,y,1);
else printf("%lld\n",search(x,y,1));
}
}
The End
c++ 线段树的更多相关文章
- bzoj3932--可持久化线段树
题目大意: 最近实验室正在为其管理的超级计算机编制一套任务管理系统,而你被安排完成其中的查询部分.超级计算机中的 任务用三元组(Si,Ei,Pi)描述,(Si,Ei,Pi)表示任务从第Si秒开始,在第 ...
- codevs 1082 线段树练习 3(区间维护)
codevs 1082 线段树练习 3 时间限制: 3 s 空间限制: 128000 KB 题目等级 : 大师 Master 题目描述 Description 给你N个数,有两种操作: 1:给区 ...
- codevs 1576 最长上升子序列的线段树优化
题目:codevs 1576 最长严格上升子序列 链接:http://codevs.cn/problem/1576/ 优化的地方是 1到i-1 中最大的 f[j]值,并且A[j]<A[i] .根 ...
- codevs 1080 线段树点修改
先来介绍一下线段树. 线段树是一个把线段,或者说一个区间储存在二叉树中.如图所示的就是一棵线段树,它维护一个区间的和. 蓝色数字的是线段树的节点在数组中的位置,它表示的区间已经在图上标出,它的值就是这 ...
- codevs 1082 线段树区间求和
codevs 1082 线段树练习3 链接:http://codevs.cn/problem/1082/ sumv是维护求和的线段树,addv是标记这歌节点所在区间还需要加上的值. 我的线段树写法在运 ...
- PYOJ 44. 【HNSDFZ2016 #6】可持久化线段树
#44. [HNSDFZ2016 #6]可持久化线段树 统计 描述 提交 自定义测试 题目描述 现有一序列 AA.您需要写一棵可持久化线段树,以实现如下操作: A v p x:对于版本v的序列,给 A ...
- CF719E(线段树+矩阵快速幂)
题意:给你一个数列a,a[i]表示斐波那契数列的下标为a[i],求区间对应斐波那契数列数字的和,还要求能够维护对区间内所有下标加d的操作 分析:线段树 线段树的每个节点表示(f[i],f[i-1])这 ...
- 【BZOJ-3779】重组病毒 LinkCutTree + 线段树 + DFS序
3779: 重组病毒 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 224 Solved: 95[Submit][Status][Discuss] ...
- 【BZOJ-3673&3674】可持久化并查集 可持久化线段树 + 并查集
3673: 可持久化并查集 by zky Time Limit: 5 Sec Memory Limit: 128 MBSubmit: 1878 Solved: 846[Submit][Status ...
- 【BZOJ-2653】middle 可持久化线段树 + 二分
2653: middle Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 1298 Solved: 734[Submit][Status][Discu ...
随机推荐
- Exchange日志
Exchange日志是exchange的重要组成部分,也是管理exchang的重要指标.exchange日志产生的速度很快,而且会占用大量磁盘空间.如何管理日志成为exchange管理员的重要管理任务 ...
- 去掉有定位的left值
left: initial; 一开始就是初始(默认值)的意思,就可以解决定位的left啦
- [ Skill ] 为什么 Lisp 语言如此先进
https://www.cnblogs.com/yeungchie/ 网上看到一个应该是 2002 年的文章 译文转自:为什么Lisp语言如此先进?(译文) - 阮一峰的网络日志 原文地址:Reven ...
- Django-初见
目录 安装&启动 HTTP请求URL路由 项目APP 返回 页面内容 给浏览器 路由 路由子表 创建数据库 定义数据库表 创建数据库表 Django Admin 读取数据库数据 过滤条件 对资 ...
- 【图像处理】Golang 获取JPG图像的宽高
一.背景 有些业务需要判断图片的宽高,来做一些图片相关缩放,旋转等基础操作. 但是图片缩放,旋转,拼接等操作需要将图片从 JPG 格式转成 RGBA 格式操作,操作完毕后,再转回 JPG 图片. 那如 ...
- C++内存空间管理
C++内存空间管理 1.C++内存机制 1.栈(Stack),函数中的局部变量,由编译器负责分配释放,函数结束,变量释放. 2.堆(Heap),通过new 申请的内存,由delete或delete[] ...
- Java 图像处理(一)
曾几何时,Java图像处理已经被认为是太过鸡肋,就连Java的创始公司,在java图像处理方面也是浅尝辄止,可能相比较C++,Java在这方面的处理,确实差强人意. 不过Java类库中有一个叫JAI的 ...
- python学习-Day26
目录 今日内容详细 编程思想 面向过程编程 面向对象编程 类与对象 概念 类与对象的创建 先定义类 后产生对象 语法结构 如何产生对象 对象的实例化 绑定方法 今日内容详细 编程思想 面向过程编程 将 ...
- netty系列之:netty中的自动解码器ReplayingDecoder
目录 简介 ByteToMessageDecoder可能遇到的问题 ReplayingDecoder的实现原理 总结 简介 netty提供了一个从ByteBuf到用户自定义的message的解码器叫做 ...
- 看完复旦博士用Python统计核酸结果后,我照着也写了一个
前几天,人民日报公众号报道了复旦博士生自己写代码,通过OCR和正则表达式统计核酸截图结果.具体文章见:https://mp.weixin.qq.com/s/l8u9JifKDlRDoz32-jZWQg ...