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 ...
随机推荐
- [MySQL]IP处理函数inet_aton()和inet_ntoa()
INET_ATON(expr) 给出一个作为字符串的网络地址的"点地址"(如127.0.0.1)表示,返回一个代表该地址数值的整数.地址可以是4或8比特地址. mysql> ...
- 适配手机端rpx像素
<script src="static/js/adaptive.js"></script> <script type="text/javas ...
- 利用window对象自带atob和btoa方法进行base64的编码和解码
项目中一般需要将表单中的数据进行编码之后再进行传输到服务器,这个时候就需要base64编码 现在可以使用window自带的方法window.atob() 和 window.btoa() 方法进行 ...
- [前端学习]vue的指令学习记录 vu-if | text | for | on | model | bind | pre
vue的指令学习记录 vue-if | text | for | on | model - 目录 vue的指令学习记录 vue-if | text | for | on | model ... 预备 ...
- 基于Apache组件,分析对象池原理
池塘里养:Object: 一.设计与原理 1.基础案例 首先看一个基于common-pool2对象池组件的应用案例,主要有工厂类.对象池.对象三个核心角色,以及池化对象的使用流程: import or ...
- 第一阶段:Java基础之数组
注意点: @Java语言是把数组当作一个"对象"来看待的 @把数组分为两部分看,一部分是数组的引用,放置在栈内存中,一部分是数组对象,放置在堆内存中 @数组的引用可以指向任何有效的 ...
- JavaSSM-总结
Spring框架技术 SSM(Spring+SpringMVC+Mybatis)阶段的学习,也算是成功出了Java新手村. 前面我们已经学习过Mybatis了. 从这里开始,很多的概念理解起来就稍微有 ...
- iOS全埋点解决方案-UITableView和UICollectionView点击事件
前言 在 $AppClick 事件采集中,还有两个比较特殊的控件: UITableView •UICollectionView 这两个控件的点击事件,一般指的是点击 UITableViewCell 和 ...
- 【课程汇总】OpenHarmony全场景Demo数字管家系列课(附链接)
小孩放学了,做作业的时间到,窗帘.护眼灯自动打开,关掉电视和扫地机,给小孩一个安静舒适的学习环境:碰到学习难题,可以随时请求你的远程指导:晚上回家休息了,选择舒适的氛围灯,伴随着睡眠音乐进入梦乡:出门 ...
- postgreSQL使用sql归一化数据表的某列,以及出现“字段 ‘xxx’ 必须出现在 GROUP BY 子句中或者在聚合函数中”错误的可能原因之一
前言: 归一化(区别于标准化)一般是指,把数据变换到(0,1)之间的小数.主要是为了方便数据处理,或者把有量纲表达式变成无量纲表达式,便于不同单位或量级的指标能够进行比较和加权. 不过还是有很多人使用 ...