浅谈线段树 Segment Tree
众所周知,线段树是algo中很重要的一项!
一.简介
线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,实际应用时一般还要开4N的数组以免越界,因此有时需要离散化让空间压缩。
二.用途
单点 : 查询(query)修改(add,mul)
区间 : 查询(区间和),修改,最大值(max),最小值(min)。

三. 实现方式
1.建树
由于每个点都表示一个区间,所以他有很多信息(左儿子,右儿子,区间sum) 所以我们用结构体存. 因为之后要用到懒标记,所以结构体还有两个懒标记。
懒标记 : 以上图为例,如果想在1 - 6区间内加一,我们就将这个信息从根节点传递到下一层,这时2,3点都有一个add = 1的懒标记,这样就表示已经加过1了,下次如果还要加,那么直接加在懒标记上。就比如你挣了一笔钱,暂时不用,就存在银行里了。之后如果求解需要递归,那么这个懒标记就向下传,并且传完后自己要清零!(这样更新后的状态就是 原状态 + 子区间点的个数 * 传下里的懒标记,(example sum = 5(原状态)+ 4(区间里有4个数,都加了个2) * 2(懒标记))-------很玄学
乘法的懒标记(luogu p3373):需要特别注意下
比如 懒标记原本为2 + 3
现在传下一个乘8 那么就变为(2 + 3) * 8
然后再传一个加三,就会变成(2 + 3 + 3) * 8
所以我们这么存 2 * 8 + 3 * 8
这样加3后值才是正确的!
上代码
代码中% P 为题目要求
struct Node {
int l, r;
ll sum;
ll add, mul;
Node() {
l = r = sum = add = ;
mul = ;
}
void update_add(ll value) {
add = (add + value) % P;
sum = (sum + (r - l + ) * value) % P;
}
void update_mul(ll value) {
sum = (sum * value) % P;
mul = (mul * value) % P;
add = (add * value) % P;
}
} t[N << ];
我的建树可能比较怪,当递归到根节点再cin,一边递归一边更新(push_up,后面有)
void build_tree(int p, int l, int r) {
t[p].l = l, t[p].r = r;
if (l == r) {
cin >> t[p].sum;
return;
}
int mid = (t[p].l + t[p].r) >> ;
build_tree(lc(p), l, mid);
build_tree(rc(p), mid + , r);
push_up(p);
}
左儿子右儿子
inline int lc(int p) {
return p << ;
}
inline int rc(int p) {
return p << | ;
}
向上push_up更新信息(sum),向下传懒标记(push_down) 切记传完后自己状态要恢复哦!
void push_up(int p) {
t[p].sum = t[lc(p)].sum + t[rc(p)].sum;
}
void push_down(int p) {
if (t[p].mul != ) {
t[lc(p)].update_mul(t[p].mul);
t[rc(p)].update_mul(t[p].mul);
t[p].mul = ;
}
if (t[p].add) {
t[lc(p)].update_add(t[p].add);
t[rc(p)].update_add(t[p].add);
t[p].add = ;
}
}
Å%%%Then
当我们进行区间改动时
(黑色为总区间,红色为需要修改的区间)
如果当前区间是全部区间的子集————那很好,咱们可以直接修改

如果当前区间和总区间有交集,那么就递归,找到第一个完全包含他的区间,然后修改,再递归上去

上代码!!!
void update1(int p, int l, int r, ll value) {//乘法更新
if (t[p].l >= l && t[p].r <= r) {
t[p].update_mul(value);
return;
}
push_down(p);
int mid = (t[p].l + t[p].r) >> ;
if (l <= mid) update1(lc(p), l, r, value);
if (r > mid) update1(rc(p), l, r, value);
push_up(p);
}
void update2(int p, int l, int r, ll value) {//加法更新
if (t[p].l >= l && t[p].r <= r) {
t[p].update_add(value);
return;
}
push_down(p);
int mid = (t[p].l + t[p].r) >> ;
if (l <= mid) update2(lc(p), l, r, value);
if (r > mid) update2(rc(p), l, r, value);
push_up(p);
}
ll query(int p, int l, int r) {//区间查询,如果是单点差距的话l == r
if (t[p].l >= l && t[p].r <= r) {
return t[p].sum % P;
}
push_down(p);
ll sum = ;
int mid = (t[p].l + t[p].r) >> ;
if (l <= mid) sum = (sum + query(lc(p), l, r)) % P;
if (r > mid) sum = (sum + query(rc(p), l, r)) % P;
return sum % P;
}
当然还可以求RMQ问题
struct Node
{
ll minn,maxx;
}t[]; //build 里加几句
t[p].maxx = max(t[lc(p)].maxx,t[rp(p)].maxx);
t[p].minn = min(t[lc(p)].minn,t[rp(p)].minn); int ans1,ans2;
void new_query(int p,int l,int r)
{
if(t[p].l == l && t[p].r == r)
{
ans1 = max(ans1,t[p].maxx);
ans2 = max(ans2,t[p].minn);
return;
}
int mid = (t[p].l + t[p].r) >> ;
if(r <= mid)
query(lc(p),l,r);
else if (l > mid)
query(rc(p),l,r);
else
{
query(lc(p),l,mid);
query(rp(p),mid + ,r);
}
}
下面附上总代码(代码按照luogu 线段树2的模板打的,可AC)
#include <iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + ;
typedef long long ll; ll P; struct Node {
int l, r;
ll sum;
ll add, mul;
// ll minn,mmax;
Node() {
l = r = sum = add = ;
mul = ;
} void update_add(ll value) {
add = (add + value) % P;
sum = (sum + (r - l + ) * value) % P;
} void update_mul(ll value) {
sum = (sum * value) % P;
mul = (mul * value) % P;
add = (add * value) % P;
}
} t[N << ]; inline int lc(int p) {
return p << ;
} inline int rc(int p) {
return p << | ;
} void push_up(int p) {
t[p].sum = t[lc(p)].sum + t[rc(p)].sum;
} void push_down(int p) {
if (t[p].mul != ) {
t[lc(p)].update_mul(t[p].mul);
t[rc(p)].update_mul(t[p].mul);
t[p].mul = ;
}
if (t[p].add) {
t[lc(p)].update_add(t[p].add);
t[rc(p)].update_add(t[p].add);
t[p].add = ;
}
} void build_tree(int p, int l, int r) {
t[p].l = l, t[p].r = r;
if (l == r) {
cin >> t[p].sum;
return;
}
int mid = (t[p].l + t[p].r) >> ;
build_tree(lc(p), l, mid);
build_tree(rc(p), mid + , r);
// t[p].maxx = max(t[lc(p)].maxx,t[rp(p)].maxx);
// t[p].minn = min(t[lc(p)].minn,t[rp(p)].minn);
push_up(p);
} void update1(int p, int l, int r, ll value) {
if (t[p].l >= l && t[p].r <= r) {
t[p].update_mul(value);
return;
}
push_down(p);
int mid = (t[p].l + t[p].r) >> ;
if (l <= mid) update1(lc(p), l, r, value);
if (r > mid) update1(rc(p), l, r, value);
push_up(p);
} void update2(int p, int l, int r, ll value) {
if (t[p].l >= l && t[p].r <= r) {
t[p].update_add(value);
return;
}
push_down(p);
int mid = (t[p].l + t[p].r) >> ;
if (l <= mid) update2(lc(p), l, r, value);
if (r > mid) update2(rc(p), l, r, value);
push_up(p);
} ll query(int p, int l, int r) {
if (t[p].l >= l && t[p].r <= r) {
return t[p].sum % P;
}
push_down(p);
ll sum = ;
int mid = (t[p].l + t[p].r) >> ;
if (l <= mid) sum = (sum + query(lc(p), l, r)) % P;
if (r > mid) sum = (sum + query(rc(p), l, r)) % P;
return sum % P;
}
/*int ans1,ans2;
void new_query(int p,int l,int r)
{
if(t[p].l == l && t[p].r == r)
{
ans1 = max(ans1,t[p].maxx);
ans2 = max(ans2,t[p].minn);
return;
}
int mid = (t[p].l + t[p].r) >> 1;
if(r <= mid)
new_query(lc(p),l,r);
else if (l > mid)
new_query(rc(p),l,r);
else
{
new_query(lc(p),l,mid);
new_query(rp(p),mid + 1,r);
}
}
*/ int main()
{
int n, m;
cin >> n >> m >> P;
build_tree(, , n);
while (m--) {
int op, l, r, num;
cin >> op >> l >> r;
if (op == || op == ) cin >> num;
if (op == ) update1(, l, r, num);
else if (op == ) update2(, l, r, num);
else cout << query(, l, r) << endl;
}
} //Juddav007 0.0
(all)
THANKS FOR WATCHING!
浅谈线段树 Segment Tree的更多相关文章
- 『线段树 Segment Tree』
更新了基础部分 更新了\(lazytag\)标记的讲解 线段树 Segment Tree 今天来讲一下经典的线段树. 线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间 ...
- 线段树(Segment Tree)(转)
原文链接:线段树(Segment Tree) 1.概述 线段树,也叫区间树,是一个完全二叉树,它在各个节点保存一条线段(即“子数组”),因而常用于解决数列维护问题,基本能保证每个操作的复杂度为O(lg ...
- 【转】Senior Data Structure · 浅谈线段树(Segment Tree)
本文章转自洛谷 原作者: _皎月半洒花 一.简介线段树 ps: _此处以询问区间和为例.实际上线段树可以处理很多符合结合律的操作.(比如说加法,a[1]+a[2]+a[3]+a[4]=(a[1]+a[ ...
- BZOJ.4695.最假女选手(线段树 Segment tree Beats!)
题目链接 区间取\(\max,\ \min\)并维护区间和是普通线段树无法处理的. 对于操作二,维护区间最小值\(mn\).最小值个数\(t\).严格次小值\(se\). 当\(mn\geq x\)时 ...
- 【数据结构系列】线段树(Segment Tree)
一.线段树的定义 线段树,又名区间树,是一种二叉搜索树. 那么问题来了,啥是二叉搜索树呢? 对于一棵二叉树,若满足: ①它的左子树不空,则左子树上所有结点的值均小于它的根结点的值 ②若它的右子树不空, ...
- 线段树(segment tree)
线段树在一些acm题目中经常见到,这种数据结构主要应用在计算几何和地理信息系统中.下图就为一个线段树: (PS:可能你见过线段树的不同表示方式,但是都大同小异,根据自己的需要来建就行.) 1.线段树基 ...
- 浅谈线段树 (例题:[USACO08FEB]酒店Hotel)By cellur925
今天我们说说线段树. 我个人还是非常欣赏这种数据结构的.(逃)因为它足够优美,有递归结构,有左子树和右子树,还有二分的思想. emm这个文章打算自用,就不写那些基本的操作了... 1° 简单的懒标记( ...
- 线段树 Interval Tree
一.线段树 线段树既是线段也是树,并且是一棵二叉树,每个结点是一条线段,每条线段的左右儿子线段分别是该线段的左半和右半区间,递归定义之后就是一棵线段树. 例题:给定N条线段,{[2, 5], [4, ...
- (转)浅谈trie树
浅谈Trie树(字典树) Trie树(字典树) 一.引入 字典是干啥的?查找字的. 字典树自然也是起查找作用的.查找的是啥?单词. 看以下几个题: 1.给出n个单词和m个询问,每次询问 ...
随机推荐
- 【原创】如何根据日志来估算线上QPS
引言 大家好,我是渣渣烟. 我又来水文章了.这篇文章我个人感觉含金量不是太大,大概5分钟左右就能看完!其实大家都知道,我不爱写这种操作型的文章,一顿截图写几个命令就搞定了,含金量不高. 然而,近期有一 ...
- 讨厌的Permission denied:adb访问手机目录时,怎么处理Permission denied问题
故事背景 手机某app出现了无响应,我想找到手机anr日志 但我只知道在data目录的某个目录里有个tra**的文件里有anr日志 具体的我真忘了,所以想要进入data中用ls查看一下 结果就出现了讨 ...
- WordPress安全防护攻略
个人近期做了一个WordPress站点,目前处于内测阶段,虽然公网还没部署起来,但是先在这学习整理一下安全防护的问题. 第一:及时更新WordPress 由于33%的互联网都在使用WordPress站 ...
- 快速入门和使用HTML–使用Django建立你的第一个网站
一 前记 你每天浏览的网页,通过网络看的新闻,看着淘宝京东的绚丽多彩的界面.是否想过这个问题,它是怎么实现的呢?有没有搜过相关的知识呢?假如没有,假如你是一位对事物好奇的主或者是做计算机相关东西的人. ...
- samba + OPENldap 搭建文件共享服务器
samba + OPENldap 搭建文件共享服务器 这里我使用的是 samba(文件共享服务) v4.9.1 + OPENldap(后端数据库软件) v2.4.44 + smbldap-tools( ...
- Spring 梳理 - JavaConfig、SPI、SCI、SpringSCI、WebApplicationInitializer、AbstractAnnotationConfigDispatcherServletInitializer、WebMvcConfigurationSupport
总结1: SCI:Servlet容器(Tomcat)提供的初始化Servlet容器本身的接口,可替换web.xml SpringSCI:SpringServletContainerInitialize ...
- MySQL 分页查询优化
有时在处理偏移量非常大的分页时候查询时,例如LIMIT 1000,10这样的查询,这时MySQL需要查询1010条记录然后只返回最后10条,前面1000条记录都被抛弃,这样的代价非常高.要优化这种查询 ...
- mybatis 配置之<typeAliases>别名配置元素设置
一.方式一:使用typeAlias <typeAliases> <typeAlias alias="User" type="com.**.entity. ...
- mysql 查询常见时间段数据
1.今天 select * from 表名 where to_days(时间字段名) = to_days(now()); 2.昨天 SELECT * FROM 表名 WHERE TO_DAYS( NO ...
- Kubernetes 系列(六):Kubernetes部署Prometheus监控
1.创建命名空间 新建一个yaml文件命名为monitor-namespace.yaml,写入如下内容: apiVersion: v1 kind: Namespace metadata: name: ...