线段树(2)——懒惰标记Lazy Tag(单运算)及例题

news/2024/11/16 20:54:37/文章来源:https://www.cnblogs.com/wuyiming1263/p/18365531

上一篇文章我们讲了线段树的最基本的操作。如果有一种操作叫做区间加法呢?这个时候显然可以依次单点修改,但是时间复杂度太高了。所以可以考虑优化,由于思考过程可能很长,此处直接引入懒惰标记。

懒惰标记就是在对一颗树的所有节点进行某种统一操作时,只对根节点做一个标记表示它的子树都要进行这个操作,但是懒惰标记仅可用于能够计算出若对树上的每个节点进行操作时,能够通过根节点直接算出查询值。这可能有些复杂,举个例子,上次的题目有一道需要区间开方根,查询的是和,对一个树开方根然后求和,这是和其子树上每一个点的值有关的,因此不能使用懒惰标记。而区间加减,只需要通过树的和加上操作数乘以子树大小即可。区间乘法,区间赋值都是可以的。

讲了这么多没用的,看看懒惰标记是怎么实现的吧。比如给区间 \([2,5]\) 同时加上 \(x\),那么假设数组总共有 \(5\) 个元素,那么线段树如下(其实就是不停的用同一个而以惹):

那么到 \([4,5]\) 的时候,很显然是完全包含的,所以给 \([4,5]\) 的懒惰标记 加上 \(x\),因为懒惰标记是有可能叠加的。左边的分别是到叶子节点 \(2\)\(3\),这个时候应该如何处理呢?其实是不用特判叶子的,因为叶子即使用懒惰标记,也不会影响,这个懒惰标记存在是合理的,如果不存在,也不会影响。那么现在要访问 \([4,4]\) 的和,遍历中会遇到有懒惰标记的节点的子结点,这个时候如果还保持懒惰标记不变显然就没有什么意义了,而且访问到 \(4\) 的值是区间增加前的。所以当需要访问有懒惰标记的结点的子结点的信息时,需要使用下放操作。下放操作即将懒惰标记的信息发放到子结点,由于懒惰标记保存的是 \(x\) 而不是 \(x \times (tr-tl+1)\),所以只需要将左右子结点的懒惰标记加上 \(lazy_now\),线段树的值加上:左子结点加上 \(lazy_now \times (mid-tl+1)\);右子结点加上 \(lazy_now \times (tr-mid)\)。然后清空 \(lazy_now\)。代码如下,设下放操作为函数 \(push_down\)

void push_down(int now,int tl,int tr){int mid=(tl+tr)/2;t[now*2]+=lazy[now]*(mid-tl+1);t[now*2+1]+=lazy[now]*(tr-mid);lazy[now*2]+=lazy[now];lazy[now*2+1]+=lazy[now];lazy[now]=0;
}

在修改和查询操作的时候,需要在判断区间在需要操作的区间完全包括内或完全不包括内之后即不完全包括时,需要加入下放操作即:

if(tl>=l&&tr<=r){...
}
if(tl>r||tr<l){...
}
push_down(now,tl,tr);

完整代码为:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,a[N],t[N*4],lazy[N*4];
//push_down:下放操作
void push_down(int now,int tl,int tr){int mid=(tl+tr)/2;t[now*2]+=lazy[now]*(mid-tl+1);t[now*2+1]+=lazy[now]*(tr-mid);lazy[now*2]+=lazy[now];lazy[now*2+1]+=lazy[now];lazy[now]=0;
}
//build:建树
void build(int now,int tl,int tr){if(tl==tr){t[now]=a[tl];return ;}int mid=(tl+tr)/2;build(now*2,tl,mid);build(now*2+1,mid+1,tr);t[now]=t[now*2]+t[now*2+1];
}
//add:now表示当前结点,tl和tr表示线段树的区间,l和r表示需要增加数值的区间,x表示增加的值
void add(int now,int tl,int tr,int l,int r,int x){if(tl>=l&&tr<=r){t[now]+=x*(tr-tl+1);lazy[now]+=x;return ;}if(tl>r||tr<l){return ;}if(lazy[now]){push_down(now,tl,tr);}int mid=(tl+tr)/2;add(now*2,tl,mid,l,r,x);add(now*2+1,mid+1,tr,l,r,x);t[now]=t[now*2]+t[now*2+1];
}
//query:查询
int query(int now,int tl,int tr,int l,int r){if(tl>=l&&tr<=r){return t[now];}if(tl>r||tr<l){return 0;}if(lazy[now]){push_down(now,tl,tr);}int mid=(tl+tr)/2;return query(now*2,tl,mid,l,r)+query(now*2,mid+1,tr,l,r);
}
int main(){n=5;a[1]=1,a[2]=2,a[3]=3,a[4]=4,a[5]=5;build(1,1,n);add(1,1,n,2,4,5);cout<<query(1,1,n,1,5);return 0;
}

区间乘法也可以仿制。注意,build的时候需要设置懒惰标记为 \(1\)。假设这里求区间和。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,a[N],t[N*4],multi[N*4];
void push_down(int now,int tl,int tr){int mid=(tl+tr)/2;t[now*2]+=multi[now]*(mid-tl+1);t[now*2+1]+=multi[now]*(tr-mid);multi[now*2]*=multi[now];multi[now*2+1]*=multi[now];multi[now]=1;
}
void build(int now,int tl,int tr){multi[now]=1;if(tl==tr){t[now]=a[tl];return ;}int mid=(tl+tr)/2;build(now*2,tl,mid);build(now*2+1,mid+1,tr);t[now]=t[now*2]+t[now*2+1];
}
void mul(int now,int tl,int tr,int l,int r,int x){if(tl>=l&&tr<=r){t[now]*=x;multi[now]+=x;return ;}if(tl>r||tr<l){return ;}if(multi[now]!=1){push_down(now,tl,tr);}int mid=(tl+tr)/2;mul(now*2,tl,mid,l,r,x);mul(now*2+1,mid+1,tr,l,r,x);t[now]=t[now*2]+t[now*2+1];
}
int query(int now,int tl,int tr,int l,int r){if(tl>=l&&tr<=r){return t[now];}if(tl>r||tr<l){return 0;}if(multi[now]!=1){push_down(now,tl,tr);}int mid=(tl+tr)/2;return query(now*2,tl,mid,l,r)+query(now*2+1,mid+1,tr,l,r);
}
int main(){n=5;a[1]=1,a[2]=2,a[3]=3,a[4]=4,a[5]=5;build(1,1,n);mul(1,1,n,2,4,5);cout<<query(1,1,n,1,5)<<" "<<query(1,1,n,4,4);return 0;
}

在做懒惰标记这类题目时,重点在于多思考,最好画个图模拟一下,看看每个地方是怎么改的,怎么下放的,思想很重要,代码也重要。线段树好写好调,对着模板多写几遍,就不容易出错了,一般在写大型题目时,建议先写线段树,然后弄几个简单的例子测一下线段树有没有写错再下一步写。

区间乘法,查询区间积;区间加法,查询区间积等都建议自己思考思考。一般来说CSP-J中线段树的题目真正应用的时候不会出的太难,所以仅仅是不会线段树想了解了解或者不打CSP-S的人可以止步这里了,接下来线段树(3)的内容要开始烧脑了。

例题

【模板】线段树 1

模板题。貌似要开long long。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1e5+10;
ll n,m,a[N],t[N*4],lazy[N*4];
void build(ll now,ll tl,ll tr){if(tl==tr){t[now]=a[tl];return ;}ll mid=(tl+tr)/2;build(now*2,tl,mid);build(now*2+1,mid+1,tr);t[now]=t[now*2]+t[now*2+1];
}
void push_down(ll now,ll tl,ll tr){ll mid=(tl+tr)/2;t[now*2]+=lazy[now]*(mid-tl+1);t[now*2+1]+=lazy[now]*(tr-mid);lazy[now*2]+=lazy[now];lazy[now*2+1]+=lazy[now];lazy[now]=0;
}
ll query(ll now,ll tl,ll tr,ll l,ll r){if(tl>=l&&tr<=r){return t[now];}if(tl>r||tr<l){return 0;}if(lazy[now]){push_down(now,tl,tr);}ll mid=(tl+tr)/2;return query(now*2,tl,mid,l,r)+query(now*2+1,mid+1,tr,l,r);
}
void add(ll now,ll tl,ll tr,ll l,ll r,ll x){if(tl>=l&&tr<=r){lazy[now]+=x;t[now]+=x*(tr-tl+1);return ;}if(tl>r||tr<l){return ;}if(lazy[now]){push_down(now,tl,tr);}ll mid=(tl+tr)/2;add(now*2,tl,mid,l,r,x);add(now*2+1,mid+1,tr,l,r,x);t[now]=t[now*2]+t[now*2+1];
}
int main(){//freopen("xx.in","r",stdin);//freopen("xx.out","w",stdout);ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n>>m;for(ll i=1;i<=n;i++){cin>>a[i];}build(1,1,n);for(ll i=1;i<=m;i++){ll opt,x,y,k;cin>>opt>>x>>y;if(opt==1){cin>>k;add(1,1,n,x,y,k);}else{cout<<query(1,1,n,x,y)<<"\n";}}return 0;
}

自行查找。由于线段树的题大多数是多个标记的,所以题目不多。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/786262.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python保存数据为xlsx格式

参考代码 运行下面的代码,首先要安装下面这两个库:pandas openpyxlimport pandas as pd processed_data = [{"日期":"20230809","品牌":"Apple"},{"日期":"20230422","品牌":"Huawei"}, ] …

[思考] Diffusion Model

时间线 以下是一些重要的里程碑,它们代表了基于Diffusion的图像生成方法的发展:时间&机构 名称 简述- VAE Variational AutoEncoder,变分自编码器用于图像生成2020.12 VQ-VAE Vector Quantized-Variational AutoEncoder,一种用于生成模型的量化技术2020.12 VQ-GAN Vect…

如何正确使用搜索引擎(屏蔽csdn)

浏览器星愿浏览器 我使用的是星愿浏览器,推荐使用,其中有个性化的设置和搜索引擎的优化辅助,搜索引擎我选择的有:百度 必应 谷歌 DuckDuckGo 检索过程中想要快速切换各种搜索引擎,星愿浏览器有提供辅助拓展插件这里主要推荐每氪净化,可以自动添加屏蔽,例如CSDN搜索后结果…

Pollard Rho 算法

Pollard Rho 算法 难评,看OI-WIKI吧。 引入 Pollard Rho 算法用于求快速找到一个正整数 \(n\) 的一个非平凡因数[1]。 生日悖论不考虑出生年份(假设每年都是365天),问:一个房间中至少多少人,才能使其中两个人生日相通的概率达到 \(50\%\)?解:假设一年有 \(n\) 天,房间…

史上最牛的 权限系统,如何设计? 来了一个 Sa-Token学习圣经

文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 : 免费赠送 :《尼恩Java面试宝典》 持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备 免费赠送 :《尼恩技术圣经+高并发系列PDF》 ,帮你 实现技术自由,…

POLIR-政治-真实社政: 理论与事实的统一与颠倒 : “改革”与“政治民主+经济市场” VS 特权集团为“既得利益”以“集权和垄断”的“假改革”忽悠人

改革有两个永恒的目标:经济的市场化 和 政治的民主化。 特权阶层和既得利益者, 为了“保住特权和既得利益”会拼力地“反对这两个目标“, 他们用“集权和垄断”的“假改革”来忽悠人们,实际上是开历史倒车。吴敬琏(经济学家)

软件工程进度报告——第八周

本周尝试练习了飞机购票问题样例1样例2

RK3588 HDMI IN调试

HDMI RX控制器配置:/* Should work with at least 128MB cma reserved above. */&hdmirx_ctrler {status = "okay";/* Effective level used to trigger HPD: 0-low, 1-high */hpd-trigger-level = <1>;hdmirx-det-gpios = <&gpio1 RK_PD5 GPIO_ACT…

阿里云服务器很久未用,服务访问异常

很久(大概一两个月)都没在使用自己的个人阿里云服务器,当自己再次访问时,竟然报错无法访问,这让自己很是意外!! 然后自己开始排查问题。 登录服务器查看docker服务,发现全部正常。 可是当自己打算重新启动时发现问题,竟然无法重启,这就很奇怪了,服务不都好好的嘛,怎么…

程序设计语言基础-有限自动机+正规式

不确定的有限自动机 NFA 该状态机在任何一个状态,基于输入的字符都不能做成一个确定的状态转换,这里分为两种状况。对于一个输入,它有两个状态可以转换。 存在ε的情况,即没有任何字符输入的情况下,NFA可以从一个状态迁移到另一个状态。确定的有限自动机 DFA 该状态机在任…

程序设计语言基础-编译过程概述+表达式

程序设计语言分类 面向机器的语言 由0、1组成的机器指令序列或汇编语言(如:move ax,bx),可读性差,难以修改和维护。 面向应用程序的语言 如,Java、C、C++、Python、Delphi、PASCAL等,更接近人类语言,提高程序设计效率。 程序设计语言分类生成目标代码过程编译程序 词法分…