线段树学习笔记(基础&进阶)(一) | P3372 【模板】线段树 1 题解
什么是线段树
线段树是一棵二叉树,每个结点存储需维护的信息,一般用于处理区间最值、区间和等问题。
线段树的用处
对编号连续的一些点进行修改或者统计操作,修改和统计的复杂度都是 O(log n)。
基础线段树(+ 懒标记)
为什么不写没有懒标记的版本?
因为我太菜的不会写 因为有懒标记的版本更实用啦。
这是一道线段树区间修改,区间查询的模板题,维护的是区间和。
1. 建树
void build(int rt, int L, int R) {
l[rt] = L, r[rt] = R;
if (L == R) {
sum[rt] = a[L];
return ;
}
int mid = L + R >> 1;
build(rt << 1, L, mid), build(rt << 1 | 1, mid + 1, R);
update(rt);
}
作为一棵最最普通的线段树,它有一个性质,对于每个结点 x,它的左儿子的编号是 x * 2 (即代码中的 x << 1
),右儿子的编号是 x * 2 + 1 (即代码中的 x << 1 | 1
)。
代码中的 l 和 r 数组用于记录编号为 rt 的点所管辖的区间的左右端点。
update
是什么?它是用子节点的数据更新自身的数据,以保证正确性。
- update 的具体实现
void update(int rt) {
sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
}
它在后续的操作中也会用到。
2. 修改
void change(int rt, int L, int R, int x) {
if (L <= l[rt] && r[rt] <= R) {
sum[rt] += (r[rt] - l[rt] + 1) * x;
lazy[rt] += x;
return ;
}
pushdown(rt);
if (L <= r[rt << 1]) change(rt << 1, L, R, x);
if (l[rt << 1 | 1] <= R) change(rt << 1 | 1, L, R, x);
update(rt);
}
其中传入的参数 L 和 R 表示需修改的区间的左右端点,x 表示这个区间上要加的数。
if (L <= l[rt] && r[rt] <= R)
意为当该点管辖的区间被需修改的区间全覆盖时,直接修改这个点上记录的区间和,并打上懒标记,不继续下传,以保证效率。(懒标记就是一种记录修改操作的 tag,用于节省时间。)
pushdown
操作意为该点管辖的区间不完全覆盖于需修改的区间时,下传懒标记。(为什么现在才下传?因为懒标记就是这么用的 因为后面的更改会用到该点的子节点的数据,如果懒标记不下传,后面的修改操作就会出现问题。)
下面的两个 if
是什么?
if (L <= r[rt << 1])
表示左儿子的区间中有部分(或全部)包含于询问区间,需统计。(如果还是没看出来就翻回去看看定义和性质吖)
那么是不是就知道 if (l[rt << 1 | 1] <= R)
是什么了?
对,它表示的是右儿子的区间中有部分(或全部)包含于询问区间,需统计。
传的参数为什么是这样不用我说了吧?
- pushdown 的具体实现
void pushdown(int rt) {
if (!lazy[rt]) return ;
sum[rt << 1] += (r[rt << 1] - l[rt << 1] + 1) * lazy[rt];
sum[rt << 1 | 1] += (r[rt << 1 | 1] - l[rt << 1 | 1] + 1) * lazy[rt];
lazy[rt << 1] += lazy[rt];
lazy[rt << 1 | 1] += lazy[rt];
lazy[rt] = 0;
update(rt);
}
在下传懒标记时给子节点的区间和加上 懒标记的值 × 子节点的区间大小。(给该子节点的管辖区间的所有数都加上懒标记的值,那么该区间的区间和就会加上 懒标记的值 × 区间长度。)
同时,给子节点的懒标记都加上自己的懒标记的值。(没错我还是给你吊在那里,不给你传到底)
记得清空自己的懒标记值并更新自己的区间和。
3. 查询
ll query(int rt, int L, int R) {
if (L <= l[rt] && r[rt] <= R) return sum[rt];
pushdown(rt);
ll res = 0;
if (L <= r[rt << 1]) res += query(rt << 1, L, R);
if (l[rt << 1 | 1] <= R) res += query(rt << 1 | 1, L, R);
return res;
}
查询其实和修改很像。(不妨肉眼观查一下)
在该结点所管辖的区间完全被覆盖时,直接返回区间和。
若未被完全覆盖,则下传懒标记,分左右儿子考虑,统计总答案并返回值。
是不是 so easy?
于是你愉快地切了这题。
完整代码,点击查看
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int, int>
inline ll read() {
ll s = 0, w = 1;
char c = getchar();
while (c < '0' || c > '9') {if (c == '-') w = -1; c = getchar();}
while (c >= '0' && c <= '9') s = (s << 3) + (s << 1) + (c ^ 48), c = getchar();
return s * w;
}
const int N = 100010;
int n, m, a[N];
struct SegmentTree {
ll sum[N << 2], lazy[N << 2];
int l[N << 2], r[N << 2];
void update(int rt) {
sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
}
void pushdown(int rt) {
if (!lazy[rt]) return ;
sum[rt << 1] += (r[rt << 1] - l[rt << 1] + 1) * lazy[rt], lazy[rt << 1] += lazy[rt];
sum[rt << 1 | 1] += (r[rt << 1 | 1] - l[rt << 1 | 1] + 1) * lazy[rt], lazy[rt << 1 | 1] += lazy[rt];
lazy[rt] = 0;
update(rt);
}
void build(int rt, int L, int R) {
l[rt] = L, r[rt] = R;
if (L == R) {
sum[rt] = a[L];
return ;
}
int mid = L + R >> 1;
build(rt << 1, L, mid), build(rt << 1 | 1, mid + 1, R);
update(rt);
}
void change(int rt, int L, int R, int x) {
if (L <= l[rt] && r[rt] <= R) {
sum[rt] += (r[rt] - l[rt] + 1) * x;
lazy[rt] += x;
return ;
}
pushdown(rt);
if (L <= r[rt << 1]) change(rt << 1, L, R, x);
if (l[rt << 1 | 1] <= R) change(rt << 1 | 1, L, R, x);
update(rt);
}
ll query(int rt, int L, int R) {
if (L <= l[rt] && r[rt] <= R) return sum[rt];
pushdown(rt);
ll res = 0;
if (L <= r[rt << 1]) res += query(rt << 1, L, R);
if (l[rt << 1 | 1] <= R) res += query(rt << 1 | 1, L, R);
return res;
}
} tree;
int main() {
n = read(), m = read();
for (int i = 1; i <= n; i++) a[i] = read();
tree.build(1, 1, n);
while (m--) {
int op = read();
if (op == 1) {
int l = read(), r = read(), x = read();
tree.change(1, l, r, x);
}
if (op == 2) {
int l = read(), r = read();
printf("%lld\n", tree.query(1, l, r));
}
}
return 0;
}
现在,你学会了区间修改区间查询的线段树,不如想想 单点修改区间查询 和 区间修改单点查询 怎么写?(为什么没有单点修改单点查询线段树呢(小声)
点击继续研究线段树 - 线段树学习笔记(基础&进阶)(二)
线段树学习笔记(基础&进阶)(一) | P3372 【模板】线段树 1 题解的更多相关文章
- zkw线段树学习笔记
zkw线段树学习笔记 今天模拟赛线段树被卡常了,由于我自带常数 \(buff\),所以学了下zkw线段树. 平常的线段树无论是修改还是查询,都是从根开始递归找到区间的,而zkw线段树直接从叶子结点开始 ...
- Python学习笔记基础篇——总览
Python初识与简介[开篇] Python学习笔记——基础篇[第一周]——变量与赋值.用户交互.条件判断.循环控制.数据类型.文本操作 Python学习笔记——基础篇[第二周]——解释器.字符串.列 ...
- 仙人掌&圆方树学习笔记
仙人掌&圆方树学习笔记 1.仙人掌 圆方树用来干啥? --处理仙人掌的问题. 仙人掌是啥? (图片来自于\(BZOJ1023\)) --也就是任意一条边只会出现在一个环里面. 当然,如果你的图 ...
- jQuery学习笔记 - 基础知识扫盲入门篇
jQuery学习笔记 - 基础知识扫盲入门篇 2013-06-16 18:42 by 全新时代, 11 阅读, 0 评论, 收藏, 编辑 1.为什么要使用jQuery? 提供了强大的功能函数解决浏览器 ...
- Django学习笔记(进阶篇)
Django学习笔记(进阶篇):http://www.cnblogs.com/wupeiqi/articles/5246483.html
- 数论算法 剩余系相关 学习笔记 (基础回顾,(ex)CRT,(ex)lucas,(ex)BSGS,原根与指标入门,高次剩余,Miller_Rabin+Pollard_Rho)
注:转载本文须标明出处. 原文链接https://www.cnblogs.com/zhouzhendong/p/Number-theory.html 数论算法 剩余系相关 学习笔记 (基础回顾,(ex ...
- 《python基础教程(第二版)》学习笔记 基础部分(第1章)
<python基础教程(第二版)>学习笔记 基础部分(第1章)python常用的IDE:Windows: IDLE(gui), Eclipse+PyDev; Python(command ...
- JSOI2008 Blue Mary开公司 | 李超线段树学习笔记
题目链接:戳我 这相当于是一个李超线段树的模板qwqwq,题解就不多说了. 代码如下: #include<iostream> #include<cstdio> #include ...
- Splay伸展树学习笔记
Splay伸展树 有篇Splay入门必看文章 —— CSDN链接 经典引文 空间效率:O(n) 时间效率:O(log n)插入.查找.删除 创造者:Daniel Sleator 和 Robert Ta ...
随机推荐
- Josephus问题(Ⅲ)
题目描述 n个人排成一圈,按顺时针方向依次编号1,2,3-n.从编号为1的人开始顺时针"一二三...."报数,报到m的人退出圈子.这样不断循环下去,圈子里的人将不断减少.最终一定会 ...
- 前端学做 PPT
前端学做 PPT 公司做技术分享.年终总结都需要用到ppt. 要快速.省事的做出高质量的 ppt,一方面需要熟练使用制作 ppt 的工具,另一方面得知道用工具做成什么样子才是好作品.前者比较简单,后者 ...
- [SWPU2019]Web1-1|SQL注入
1.打开之后界面如下: 2.查看源代码.登录注入等未发现有用信息,结果如下: 3.进行注册试试,注册时发现admin账户已被注册,随便注册一个账户并登录,结果如下: 申请发布广告页面如下: 4.发布广 ...
- EF Core 数据过滤
1 前言 本文致力于将一种动态数据过滤的方案描述出来(基于 EF Core 官方的数据筛选器),实现自动注册,多个条件过滤,单条件禁用(实际上是参考ABP的源码),并尽量让代码保持 EF Core 的 ...
- 常用的函数式接口_Consumer接口和常用的函数式接口_Consumer接口的默认方法andThen
Consumer接口 java,util.function.Consumer接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定 抽象方法:accept C ...
- 对象数组的foreach循环操作和集合容器
ForEach标签可以循环数组,list,map集合 采用 foreach循环遍历 ,并每次循环允许执行一次回调函数 for (容器中元素类型 临时变量: 容器变量) { System.out.pri ...
- JavaWeb--Servlet详解
前言 Java Web 其实就是一个技术的总和,把Web看成一个容器而已主要使用JavaEE技术来实现.在加上各种中间件. 整个javaWeb阶段的内容通过实际的案例贯穿学习, 所涉及到的技术知识点会 ...
- 聚是一团火散作满天星,前端Vue.js+elementUI结合后端FastAPI实现大文件分片上传
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_175 分片上传并不是什么新概念,尤其是大文件传输的处理中经常会被使用,在之前的一篇文章里:python花式读取大文件(10g/50 ...
- SElinux管理
SElinux: 是Linux的一个强制访问控制的安全模块 SElinux的相关概念: 对象:文件.目录.进程.端口等 主体:进程称为主体 SElinux将所有的文件都赋予一个type类型的标签,所有 ...
- 分布式二级缓存组件实战(Redis+Caffeine实现)
前言 在生产中已有实践,本组件仅做个人学习交流分享使用.github:https://github.com/axinSoochow/redis-caffeine-cache-starter 个人水平有 ...