算法:线段树&&Luogu p3372题解

前言

不愧是线段树,竟然卡我这么久,还是那句话:

十年OI一场空,不开long long见祖宗

#1 什么是线段树?

  • 线段树长什么样?

通俗一点,线段树就是线段,树
实际上,线段树是一棵完全二叉搜索树
我们对于线段树模型的理解,在于它的每一个节点都维护了一定的线段区间,而该节点的两个儿子节点分别二分维护这个区间,就这样一直二分下去,直到整个序列被二分殆尽。
我们可以用简单的进制数学严格的证明线段树可以维护到所有区间。

  • 线段树可以干什么?

刚才所说的维护,范围很广,可以求区间最值,求和,求乘积,除了在线性序列上的线段树,还有维护二维数组的矩阵树和三维数组的空间树
我们一般研究一维线段树。

  • 误区?
    ST表和线段树的区别:ST表不带修,线段树带修。

#2 如何建立一棵线段树。

首先,对于一棵完全二叉树,如果用一维数组来存的话,节点 k 的左子和右子就分别是 k2k2+1 ,我们改成位运算就是 k<<1k<<1|1
所以,线段树的数组占空间很大,建议开 N 的四倍。
那么我们可以通过如下代码建立一棵维护区间和线段树:

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】

posted @   MornHus  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示