关于线段树

线段数是一种区间树



可以看出:叶子即为输入的数

假设一个节点为 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

作用:构建线段树

思路:

  1. 给当前的 l 和 r 区间赋值
  2. 判断是否为叶子节点,是就把当前位置的 sum 赋为 input[l] 并返回
  3. 否则继续构建
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 也是包含

思路:

  1. 先将当前位置的 sum 加上 k
  2. 如果达到叶子,返回
  3. 判断左右子树是否包含并继续更新
  4. 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;
}
}
递归修改

思路:

  1. 如果该区间被完全包含,更新 sum 打上标记并返回
  2. down
  3. 判断左右区间是否包含并继续更新
  4. 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);
}

单点查询

思路:

  1. 如果找到该点,返回 sum
  2. 判断左右区间是否包含并继续查找
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);
}

区间查询

思路:

  1. 如果区间被完全包含,返回 sum
  2. 判断左右区间是否包含并把查找的值加到 s
  3. 返回 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

  1. 如果遇到需要区间修改的,查询时一定要下传标记
  2. 十年 OI 一场空,不开 long long 见祖宗

区改 + 区查

洛谷 P3372

#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));
}
}

区改 + 单查

洛谷 P3368

#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));
}
}

单改 + 区查

洛谷 P3374

#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));
}
}

复杂的区间操作

区间乘法

例题

洛谷 P3373

解析

多了一个懒标记 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));
}
}

区间开方

例题

洛谷 P4145

解析

这题的突破口在于: \(\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++ 线段树的更多相关文章

  1. bzoj3932--可持久化线段树

    题目大意: 最近实验室正在为其管理的超级计算机编制一套任务管理系统,而你被安排完成其中的查询部分.超级计算机中的 任务用三元组(Si,Ei,Pi)描述,(Si,Ei,Pi)表示任务从第Si秒开始,在第 ...

  2. codevs 1082 线段树练习 3(区间维护)

    codevs 1082 线段树练习 3  时间限制: 3 s  空间限制: 128000 KB  题目等级 : 大师 Master 题目描述 Description 给你N个数,有两种操作: 1:给区 ...

  3. codevs 1576 最长上升子序列的线段树优化

    题目:codevs 1576 最长严格上升子序列 链接:http://codevs.cn/problem/1576/ 优化的地方是 1到i-1 中最大的 f[j]值,并且A[j]<A[i] .根 ...

  4. codevs 1080 线段树点修改

    先来介绍一下线段树. 线段树是一个把线段,或者说一个区间储存在二叉树中.如图所示的就是一棵线段树,它维护一个区间的和. 蓝色数字的是线段树的节点在数组中的位置,它表示的区间已经在图上标出,它的值就是这 ...

  5. codevs 1082 线段树区间求和

    codevs 1082 线段树练习3 链接:http://codevs.cn/problem/1082/ sumv是维护求和的线段树,addv是标记这歌节点所在区间还需要加上的值. 我的线段树写法在运 ...

  6. PYOJ 44. 【HNSDFZ2016 #6】可持久化线段树

    #44. [HNSDFZ2016 #6]可持久化线段树 统计 描述 提交 自定义测试 题目描述 现有一序列 AA.您需要写一棵可持久化线段树,以实现如下操作: A v p x:对于版本v的序列,给 A ...

  7. CF719E(线段树+矩阵快速幂)

    题意:给你一个数列a,a[i]表示斐波那契数列的下标为a[i],求区间对应斐波那契数列数字的和,还要求能够维护对区间内所有下标加d的操作 分析:线段树 线段树的每个节点表示(f[i],f[i-1])这 ...

  8. 【BZOJ-3779】重组病毒 LinkCutTree + 线段树 + DFS序

    3779: 重组病毒 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 224  Solved: 95[Submit][Status][Discuss] ...

  9. 【BZOJ-3673&3674】可持久化并查集 可持久化线段树 + 并查集

    3673: 可持久化并查集 by zky Time Limit: 5 Sec  Memory Limit: 128 MBSubmit: 1878  Solved: 846[Submit][Status ...

  10. 【BZOJ-2653】middle 可持久化线段树 + 二分

    2653: middle Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1298  Solved: 734[Submit][Status][Discu ...

随机推荐

  1. Exchange日志

    Exchange日志是exchange的重要组成部分,也是管理exchang的重要指标.exchange日志产生的速度很快,而且会占用大量磁盘空间.如何管理日志成为exchange管理员的重要管理任务 ...

  2. 去掉有定位的left值

    left: initial;   一开始就是初始(默认值)的意思,就可以解决定位的left啦

  3. [ Skill ] 为什么 Lisp 语言如此先进

    https://www.cnblogs.com/yeungchie/ 网上看到一个应该是 2002 年的文章 译文转自:为什么Lisp语言如此先进?(译文) - 阮一峰的网络日志 原文地址:Reven ...

  4. Django-初见

    目录 安装&启动 HTTP请求URL路由 项目APP 返回 页面内容 给浏览器 路由 路由子表 创建数据库 定义数据库表 创建数据库表 Django Admin 读取数据库数据 过滤条件 对资 ...

  5. 【图像处理】Golang 获取JPG图像的宽高

    一.背景 有些业务需要判断图片的宽高,来做一些图片相关缩放,旋转等基础操作. 但是图片缩放,旋转,拼接等操作需要将图片从 JPG 格式转成 RGBA 格式操作,操作完毕后,再转回 JPG 图片. 那如 ...

  6. C++内存空间管理

    C++内存空间管理 1.C++内存机制 1.栈(Stack),函数中的局部变量,由编译器负责分配释放,函数结束,变量释放. 2.堆(Heap),通过new 申请的内存,由delete或delete[] ...

  7. Java 图像处理(一)

    曾几何时,Java图像处理已经被认为是太过鸡肋,就连Java的创始公司,在java图像处理方面也是浅尝辄止,可能相比较C++,Java在这方面的处理,确实差强人意. 不过Java类库中有一个叫JAI的 ...

  8. python学习-Day26

    目录 今日内容详细 编程思想 面向过程编程 面向对象编程 类与对象 概念 类与对象的创建 先定义类 后产生对象 语法结构 如何产生对象 对象的实例化 绑定方法 今日内容详细 编程思想 面向过程编程 将 ...

  9. netty系列之:netty中的自动解码器ReplayingDecoder

    目录 简介 ByteToMessageDecoder可能遇到的问题 ReplayingDecoder的实现原理 总结 简介 netty提供了一个从ByteBuf到用户自定义的message的解码器叫做 ...

  10. 看完复旦博士用Python统计核酸结果后,我照着也写了一个

    前几天,人民日报公众号报道了复旦博士生自己写代码,通过OCR和正则表达式统计核酸截图结果.具体文章见:https://mp.weixin.qq.com/s/l8u9JifKDlRDoz32-jZWQg ...