关于线段树

线段数是一种区间树



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

假设一个节点为 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. vue引入echarts

    效果图: 1.安装Echarts :     npm install echarts -S 或者使用国内的淘宝镜像: 安装: npm install -g cnpm --registry=https: ...

  2. Linux上安装RePlace

    RePlAce: Advancing Solution Quality and Routability Validation in Global Placement 项目地址 https://gith ...

  3. 使用Visual Studio 2019开发Qt程序

    安装Qt 如标题,你首先需要到 http://download.qt.io/ 去下载并安装Qt,并在引导下安装MSVC组件(这里不做过多解释) Visual Studio 2019 配置 打开VS20 ...

  4. vue elementUI 之 this.$confirm 用法

      this.$confirm('您确定退出当前账号吗?', '提示', {                         confirmButtonText: '确定',             ...

  5. Codeforces Round #703 (Div. 2)__ B. Eastern Exhibition__ 纯纯的思维

    原题链接https://codeforces.com/contest/1486/problem/B 题目 解题思路 这是个思维题,  算是货仓选址的变式, 想要到达各个点距离最小,我们的目标可以化为先 ...

  6. 倒计时第3天!Google Summer of Code报名即将截止!(Casbin社区还有空缺名额)

    Google Summer of Code 介绍 Google Summer of Code ( GSoC ,即 Google 编程之夏)是 Google (谷歌)组织并提供经费,面对全球在读学生的在 ...

  7. 论文解读(GROC)《Towards Robust Graph Contrastive Learning》

    论文信息 论文标题:Towards Robust Graph Contrastive Learning论文作者:Nikola Jovanović, Zhao Meng, Lukas Faber, Ro ...

  8. python中的sort用法

    内置的列表类型提供sort的方法 可以根据多项指标给list实例中的元素排序.在默认情况下,sort方法总是按照自然升序排列列表内的元素 #升序排列 list1=[2,3,1,2,5] list1.s ...

  9. go convert slice to struct

    Question: in golang how to convert slice to struct scene 1:use reflect convert slice to struct func ...

  10. vuepress搭建UI组件库文档踩坑篇

    为了实现组件效果预览及代码展示可折叠功能,使用了插件vuepress-plugin-demo-container 相关配置可参考官网说明文档 第一步 安装插件 npm i - D vuepress-p ...