算法:线段树&&Luogu p3372题解
前言
不愧是线段树,竟然卡我这么久,还是那句话:
十年OI一场空,不开long long见祖宗
#1 什么是线段树?
- 线段树长什么样?
通俗一点,线段树就是线段,树。
实际上,线段树是一棵完全二叉搜索树。
我们对于线段树模型的理解,在于它的每一个节点都维护了一定的线段区间,而该节点的两个儿子节点分别二分维护这个区间,就这样一直二分下去,直到整个序列被二分殆尽。
我们可以用简单的进制数学严格的证明线段树可以维护到所有区间。
- 线段树可以干什么?
刚才所说的维护,范围很广,可以求区间最值,求和,求乘积,除了在线性序列上的线段树,还有维护二维数组的矩阵树和三维数组的空间树
我们一般研究一维线段树。
- 误区?
ST表和线段树的区别:ST表不带修,线段树带修。
#2 如何建立一棵线段树。
首先,对于一棵完全二叉树,如果用一维数组来存的话,节点
所以,线段树的数组占空间很大,建议开
那么我们可以通过如下代码建立一棵维护区间和线段树:
int n;
long long a[100001];//原数组
long long t[100001<<2];//线段树数组
void pushup(int k){//这是更新父亲节点的函数
t[k]=(t[k<<1]+t[k<<1|1]);//很简单的加法
}
void build(int k,int l,int r){//k:线段树上节点编号,l、r:该节点表示的区间左右端点
if(l==r){//如果已经达到叶子节点
t[k]=a[l];//直接更新
}else{
int mid=l+((r-l)>>1);//区间中点
build(k<<1,l,mid);//建左子
build(k<<1|1,mid+1,r);//建右子
pushup(k);//更新父亲节点,这一步必须有
}
}
#3 线段树的有关操作
* 区间修改
我们在进行区间修改的时候,同样使用到了二分、递归的思想。
在这里我们遇到了一个问题,如何在保证效率的同时,达到区间修改的效果
我们引入一个概念:Lazy-tag
它的主要作用是:当我们发现当前搜索的节点代表的区间真包含(⊊)于修改区间时,我们直接修改当前节点,并且给这个节点打一个lazy-tag,意思是这个节点一下的节点假装修改过了。当我们需要修改或查找时,一旦搜到了标记的节点,直接把标记扔给儿子节点,并且更新儿子节点的值,这个扔给儿子的操作叫pushdown,具体看代码:
ll lazy[100001<<2];//懒惰标记数组
void pushdown(int k,int l,int r){
if(lazy[k]){
lazy[k<<1]+=lazy[k];//左子打标
lazy[k<<1|1]+=lazy[k];//右子打标
int mid=l+((r-l)>>1);//中间点
t[k<<1]+=lazy[k]*(mid-l+1);//更新左子
t[k<<1|1]+=lazy[k]*(r-mid);//更新右子
lazy[k]=0;//消掉这个标记
}
}
void update(int L,int R,ll val,int l,int r,int k){
if(L<=l&&r<=R){//如果真包含
lazy[k]+=val;//打标
t[k]+=(r-l+1)*val;//更新
}else{
pushdown(k,l,r);//下推
int mid=l+((r-l)>>1);
if(L<=mid){
update(L,R,val,l,mid,k<<1);
}
if(R>mid){
update(L,R,val,mid+1,r,k<<1|1);
}
pushup(k);//上推
}
}
* 区间查询
区间查询就很简单了,直接递归ok。
ll query(int L,int R,int l,int r,int k){
if(L<=l&&r<=R){
return t[k];//真包含则返回
}else{
pushdown(k,l,r);//下推
ll res=0;
int mid=l+((r-l)>>1);
if(L<=mid){
res+=query(L,R,l,mid,k<<1);
}
if(R>mid){
res+=query(L,R,mid+1,r,k<<1|1);
}
return res;//返回
}
}
#3 题解时间
线段树模板,完整代码如下:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll read(){
ll x=0;
int f=1;
char c=getchar();
while(c>'9'||c<'0'){
if(c=='-')f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<1)+(x<<3)+(c^'0');
c=getchar();
}
return x;
}
int n;
ll a[100001];
ll t[100001<<2];
ll lazy[100001<<2];
void pushdown(int k,int l,int r){
if(lazy[k]){
lazy[k<<1]+=lazy[k];
lazy[k<<1|1]+=lazy[k];
int mid=l+((r-l)>>1);
t[k<<1]+=lazy[k]*(mid-l+1);
t[k<<1|1]+=lazy[k]*(r-mid);
lazy[k]=0;
}
}
void pushup(int k){
t[k]=(t[k<<1]+t[k<<1|1]);
}
void build(int k,int l,int r){
if(l==r){
t[k]=a[l];
}else{
int mid=l+((r-l)>>1);
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
pushup(k);
}
}
void update(int L,int R,ll val,int l,int r,int k){
if(L<=l&&r<=R){
lazy[k]+=val;
t[k]+=(r-l+1)*val;
}else{
pushdown(k,l,r);
int mid=l+((r-l)>>1);
if(L<=mid){
update(L,R,val,l,mid,k<<1);
}
if(R>mid){
update(L,R,val,mid+1,r,k<<1|1);
}
pushup(k);
}
}
ll query(int L,int R,int l,int r,int k){
if(L<=l&&r<=R){
return t[k];
}else{
pushdown(k,l,r);
ll res=0;
int mid=l+((r-l)>>1);
if(L<=mid){
res+=query(L,R,l,mid,k<<1);
}
if(R>mid){
res+=query(L,R,mid+1,r,k<<1|1);
}
return res;
}
}
int main(){
n=read();int m=read();
for(int i=1;i<=n;i++){
a[i]=read();
}
build(1,1,n);
for(int i=1;i<=m;i++){
int a,b,c;
long long d;
a=read();
if(a==1){
b=read();
c=read();
d=read();
update(b,c,d,1,n,1);
}else{
b=read();
c=read();
printf("%lld\n",query(b,c,1,n,1));
}
}
return 0;
}
后记
一开始觉得线段树挺难的,其实……也就那样【doge】
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!