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 ...
随机推荐
- 小程序生成海报demo
效果图: <view class='poste_box' id='canvas-container' style="margin:0 auto;border-radius:16rpx; ...
- 跳转到下一页面时,必须先勾选阅读xx须知/协议才可跳转功能
当跳转到新的html页面时,先判断checkbox是否选中,选中-->跳转到新页面:未选中--弹出提示消息(请先同意须知) HTML: 1 <div class="choose& ...
- BI系统打包Docker镜像及容器化部署的具体实现
在过去的几年中,"云"作为明星热词站在了各种新潮技术之中,你可能使用过,但说不清它的原理:或者是没用过,但听过它的大名:也可能连它的名字都没听过,但你对这只蓝色鲸鱼一定十分眼熟.作 ...
- mysql查询 if判断、case语句的使用等
一. 查询的数字转换为中文返回前端 1. 如果是0或1表状态等,可用: IF(字段 = 0, '否', '是') AS xxx 2. 如果是多个值,比如1,2,3可用: ELT(字段, '计划治理', ...
- 制作Unity中的单位血条
本文章用于记录Unity的学习过程,如有疑问,欢迎交流. 1.血条的显示 在Unity场景中创建空物体,然后新建两个Image(图片),当然只用一个也行,一个作为填充来显示血量,一个作为血条的外框. ...
- 几种比较经典的波形及其FFT变换(正弦波,三角波,方波和锯齿波)
之前上学时我的信号学得最差了,主要原因还是我高数学得不怎么样.可能是人总敬畏自己最不会的,所以我觉得我学过诸多科目中,数学是最博大精深而最妙的,从最开始的一次函数到反比例函数,二次三次函数和双曲线,椭 ...
- el-table高度问题
1. 外层容器flex=1,el-table设置属性height="calc(100% - 60px),有时一刷新页面表格高度无限增加,滚动条一直变短 试验过几次后发现是因为el-table ...
- 基于深度学习的人脸性别识别系统(含UI界面,Python代码)
摘要:人脸性别识别是人脸识别领域的一个热门方向,本文详细介绍基于深度学习的人脸性别识别系统,在介绍算法原理的同时,给出Python的实现代码以及PyQt的UI界面.在界面中可以选择人脸图片.视频进行检 ...
- Linux下安装并使用jumpserver跳板机
堡垒机介绍: 官方网站:https://www.jumpserver.org/ 在一个特定网络环境下,为了保障网络和数据不受外界入侵和破坏,而运用各种技术手段实时收集和监控网络环境中每一个组成部分的系 ...
- @Resource与构造函数踩坑
(虽然解决了需求,但我还是没搞懂为什么构造函数结束后,调用userMapper注入还是为空!) 首先,我有一个没有问题的userMapper类,用于处理user的数据库处理. 其次,我在另一个类里面使 ...