线段树题单记录

news/2024/10/5 7:06:53/文章来源:https://www.cnblogs.com/CLydq-Blogs/p/18274946

线段树题单记录

线段树的题都很板的,模板敲上去再改改就行

P3372 【模板】线段树 1

题目

Link

为什么模板是绿题,还有下面那道

思路

首先我们要明白它为什么叫线段树:

OI Wiki 上的这张图很好理解:

BinaryTree

从这张图也可以看出来,线段树的每个节点管辖的一个又一个的线段(区间),所以我们通俗地叫它线段树。

废话

这里只讲最简单的线段树,关于什么 \(ZKW\) 线段树、动态开点 请自行了解。

普通线段树学完了好像那些奇奇怪怪的线段树优化更好理解

现在给出你一个数组的值,然后让你区间修改,区间求和 (当然树状数组也能做,就是麻烦了亿点),这就是线段树最基本的功能。当然,它也可以维护区间最值什么的,看下面的题目就知道了。

因为线段树的时空复杂度大约是树状数组和 \(st\) 表的 \(4\) 倍,所以能用树状数组或 \(st\) 表写的题目尽量不要用线段树去写。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
#define int long long
inline int read(){int x=0,f=1;char ch=getchar();while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}return x*f;
}
struct node{int l,r;int s;int lt;
}t[N*4];
int n,m;
int a[N];
void pushup(int p){t[p].s=t[p*2].s+t[p*2+1].s;}
void pushdown(int p){if(t[p].lt==0)return;t[p*2].s+=t[p].lt*(t[p*2].r-t[p*2].l+1);t[p*2+1].s+=t[p].lt*(t[p*2+1].r-t[p*2+1].l+1);t[p*2].lt+=t[p].lt;t[p*2+1].lt+=t[p].lt;t[p].lt=0;
}
void build(int p,int l,int r){t[p].l=l;t[p].r=r;if(l==r){t[p].s=a[l];return;}int mid=(l+r)>>1;build(p*2,l,mid);build(p*2+1,mid+1,r);pushup(p);
}
void update(int p,int l,int r,int c){if(t[p].l>=l&&t[p].r<=r){t[p].lt+=c;t[p].s+=c*(t[p].r-t[p].l+1);return;}pushdown(p);int mid=(t[p].l+t[p].r)>>1;if(l<=mid)update(p*2,l,r,c);if(r>mid)update(p*2+1,l,r,c);pushup(p);
}
int query(int p,int l,int r){if(t[p].l>=l&&t[p].r<=r)return t[p].s;int reu=0;int mid=(t[p].l+t[p].r)>>1;pushdown(p);if(l<=mid)reu+=query(p*2,l,r);if(r>mid)reu+=query(p*2+1,l,r);return reu;
}
signed main(){cin>>n>>m;for(int i=1;i<=n;i++)cin>>a[i];build(1,1,n);int ty;int x,y,k;for(int i=1;i<=m;i++){cin>>ty;if(ty==1){cin>>x>>y>>k;update(1,x,y,k);}else{cin>>x>>y;cout<<query(1,x,y)<<endl;}}return 0;
}

建树复杂度:\(O (NlogN)\)

查询复杂度:\(O (logN)\)

修改复杂度:\(O (logN)\)

代码解释

变量声明

结构体 node:树的结点,包含左右端点和保存的信息什么的。

数组 t:树的数组

数组 a:原始数组

int 整型 n:原始数组长度

int 整型 m:操作个数

build

定义:void build (int p,int l,int r)

传入三个参数:节点编号 \(p\),该节点的左端点 \(l\) 和右端点 \(r\)

然后对其进行赋值 t [p].l=l;t [p].r=r;(有些题目可能要赋的值比较多)。

然后递归建树:

    if(l==r){t[p].s=a[l];return;}int mid=(l+r)>>1;build(p*2,l,mid);build(p*2+1,mid+1,r);pushup(p);

如果说当前节点的左右端点编号相等,就证明它已经建到叶子节点了,就可以赋值 return 了。

否则,就将区间不严格对半拆开,然后递归建树,然后 pushup

然后你就看到了突然出现的 pushup

pushup

声明:void pushup (int p)

其中 \(p\) 是节点编号。该节点的和等于它左右儿子的和的和。有些题目可能要维护的东西比较多,比如下面的 P1471 方差 那道题。

它的作用很简单,就是在儿子节点修改后将修改值给向上推给父亲:{t [p].s=t [p*2].s+t[p*2+1].s;}

现在树建完了,然后该维护和查询了。

update

声明:void update (int p,int l,int r,int c)

传入 \(4\) 个参数:

当前节点 \(p\)、当前修改的左端点 \(l\)、当前修改的右端点 \(r\)、当前修改值 \(c\)

如果说当前节点的左右端点已经被完全包含了,那么就在当前节点修改并 return

	if(t[p].l>=l&&t[p].r<=r){t[p].lt+=c;t[p].s+=c*(t[p].r-t[p].l+1);return;}

否则,继续往下递归修改:

	pushdown(p);int mid=(t[p].l+t[p].r)>>1;if(l<=mid)update(p*2,l,r,c);if(r>mid)update(p*2+1,l,r,c);pushup(p);

于是我们就看到了 pushdown 函数和 莫名其妙的 \(lt\) 变量。

先来解释 \(lt\)

\(lt\)

它是 \(LazyTag\) 的缩写,中文即懒标记。

它的作用很简单。

当我们修改到某一个节点,而这个节点被修改区间包含时,我们就可以直接修改该区间的值。

但是由于它的儿子没有被修改,所以我们需要记录已经修改的值再 return

如果下一次经过这个节点,就先将 \(lt\)\(pushdown\) 下去,以保证儿子的和是最新的。否则,可能你的某次修改就会被吞掉。

那么 pushdown 的作用就很明显了。

pushdown

声明:void pushdown (int p)

传入要进行 pushdown 操作的节点编号 \(p\),然后进行修改,就像这样:

{if(t[p].lt==0)return;t[p*2].s+=t[p].lt*(t[p*2].r-t[p*2].l+1);t[p*2+1].s+=t[p].lt*(t[p*2+1].r-t[p*2+1].l+1);t[p*2].lt+=t[p].lt;t[p*2+1].lt+=t[p].lt;t[p].lt=0;
}

那个 if (t [p].lt==0) return; 是用来进行判断的:如果当前节点没有需要进行 pushdown 操作的懒标记就 return。没有它也行,它主要是用来加快程序运行的。

这里也是题目设难点的一个重灾区,某些题目要考虑的情况很多,一不注意就 \(WA\)

现在我们来看查询 query。

query

声明:int query (int p,int l,int r)

传入三个参数:

当前节点编号 \(p\)、当前修改的左端点 \(l\)、当前修改的右端点 \(r\)

update 差不多,query 的判断逻辑也是如果说当前节点的左右端点已经被查询区间完全包含了,那么就 return 当前节点的查询值:

	if(t[p].l>=l&&t[p].r<=r)return t[p].s;

否则就接着往下查询:

    int reu=0;int mid=(t[p].l+t[p].r)>>1;pushdown(p);if(l<=mid)reu+=query(p*2,l,r);if(r>mid)reu+=query(p*2+1,l,r);return reu;

至此,整个线段树的核心代码就讲完了。

VSCode:277 行真 6

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

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

相关文章

PTA训练集阶段总结性blog(第三弹)

目录PTA训练集总结blog1.前言2.设计与分析题目集七 7.1家居强电电路模拟系统—3关于设计要求:UML类图及设计分析:部分源码:复杂度分析:题目集八 7.1 家具强电电路模拟系统—8关于设计要求:部分源码:复杂度分析:3.踩坑心得4.改进建议5.总结 PTA训练集总结blog 1.前言 1.这…

智慧工厂视频智能分析系统解决方案 燧机AI算法

视频监控智能分析系统通过安装在工厂生产施工作业现场的各个监控装置,构建智能监控分析预警和防范体系,有效弥补传统方法和技术在管理中的缺陷。智慧工厂视频监控智能分析系统实现对人员、机械、材料、环境的全方位实时监控,变被动“监督”为主动“监控”;真正做到事前预警…

从零开始学Spring Boot系列-集成Spring Security实现用户认证与授权

在Web应用程序中,安全性是一个至关重要的方面。Spring Security是Spring框架的一个子项目,用于提供安全访问控制的功能。通过集成Spring Security,我们可以轻松实现用户认证、授权、加密、会话管理等安全功能。本篇文章将指导大家从零开始,在Spring Boot项目中集成Spring S…

springboot+vue前后端分离项目-vue项目搭建4

1.修改vue布局,app.vue为入口,只引入路由,路由控制页面显示 改造后app.vue 新增layout.vue 修改/router/index.js,默认访问路径 / 对应layout.vue,layout里的路由是嵌套路由,/home匹配到HomeView.vue新增vue/src/views/LoginView.vue 效果以上仅供参考,如有疑问,留言联…

OOP 7-8次作业总结

23201636-曾繁榕 目录前言 设计与分析 踩坑心得 改进建议 总结 前言 本次作业知识点:类与对象的复杂使用,继承的进阶使用,各种接口的实现,复杂电路的处理,一个对象多个数据的储存输出,还主要考察了正则表达式的运用,字符串的提取分割; 个人体会:本次作业难度较难,题量适…

DHCP

DHCP Discover:由客户端发出广播,用来发现DHCP服务器。 DHCP Offer:由DHCP服务器发出,告诉客户端,我可以提供IP地址。 DHCP Request:由客户端发出的广播,告诉对应的DHCP服务器,我需要IP地址。 DHCP ACK:由DHCP服务器发出,提供客户端响应的IP地址,DNS,租期,掩码,网…

基于cifar数据集合成含开集、闭集噪声的数据集

前言 噪声标签学习下的一个任务是:训练集上存在开集噪声和闭集噪声;然后在测试集上对闭集样本进行分类。 训练集中被加入的开集样本,会被均匀得打上闭集样本的标签充当开集噪声;而闭集噪声的设置与一般的噪声标签学习一致,分为对称噪声:随机将闭集样本的标签替换为其他类…

AbpVnext系列四 用户表设计

一、一个系统中,最开始要设计的是用户表,先简单的设计如下图。 二、类的实体/// <summary>/// /// </summary>/// <param name="id"></param>public User(long id){Id = id;Status = NormalLockedStatus.Normal;}/// <summary>/// …

oop第7~8次作业总结(第三次Blog)

oop第7~8次作业总结(第三次Blog) 一、前言 二、设计与分析 三、踩坑心得 四、改进建议 五、总结 一、前言 1、第七次作业 第六次作业只有一道题,与上次相比,本次迭代考虑多个并联电路串联在一起的情况,考虑一条串联电路中包含其他串联电路的情况。增加了互斥开关和受控窗帘…

Profinet IO从站数据 转EtherCAT项目案例

目录 1 案例说明 1 2 VFBOX网关工作原理 1 3 准备工作 2 4 使用PRONETA软件获取PROFINET IO从站的配置信息 2 5 设置网关采集PROFINETIO从站设备数据 5 6 启动ETHERCAT从站转发采集的数据 8 7 选择槽号和数据地址 9 8 选择子槽号 11 9 案例总结 12 1 案例说明设置网关采集Profi…

题目集7~8总结性blog

前言在我看来,题目集7~8知识点在于类的使用,方法的调用,通过不同类的ArrayList的使用将所有元件信息录入,通过连接方式将其变成一条电路,从而进行计算和使用,题量不大,只有一道题,在精不在多,难度适中,可以接受并且能够编程写出,完成功能的实现。设计与分析题目集七…