线段树 (学习笔记)(25.11.4)

线段树 (学习笔记)

基础线段树

基础线段树概述

将一个总体的数组分为不同的树形结构,可以让区间修改区间查询成为\(O(logn)\)级别的时间复杂度

基础线段树实现

建树部分,需要去处理一个树的结构体,用于存储权值,左右边界,数组编号

void build(int p, int l, int r){
	t[p].l = l, t[p].r = r;
 //这里是在遇见边界的时候传入数值
 if(l==r){
     t[p].pre = a[l];
     return 0;
 }
 int mid +(l+((r-l)>>1));
 //递归建立左右子树
 build(p*2, l, mid), build(p*2+1, mid+1, r);
 t[p].pre=t[p*2].pre+t[p*2+1].pre;
}

修改部分,需要去做区间的查询细分,可以满足对于区间的log级别的修改,思维与树状数组类似

void change(int p, ll x, ll y, ll z){
 //这里表示,如果区间被完全覆盖,那么就直接给区间打上懒标记,不去向下查询了,节省时间
 if(x<=t[p].l && y>=t[p].r){
     t[p].pre+=z*(t[p].r-t[p].l+1);
     t[p].add+=z;
     return;
 }
 //如果区间没有被完全覆盖,就下传懒标记,因为儿子区间未被更改
 spread(p);
 int mid = t[p].l+((t[p].r-t[p].l)>>1);
 //递归修改
 if(x<=mid) change(p*2,x,y,z);
 if(y<mid) change(p*2+1,x,y,z);
 //修改值
 t[p].pre = t[p*2].pre+t[p*2+1].pre;
}

懒标记

因为如果每次修改/查询都要去把每个区间查询完毕并且做到修改,这样子很可能时间会爆炸,所以我们创造出懒标记,字面意思,懒,避免去多做一些事情,所以如果一个区间被完全覆盖并修改,这个时候我们就无需再细分向下查找每个子区间了,所以直接用一个懒标记表示该区间被改了,在使用的时候再去计算

void spread(int p){
 //首先如果这个点有懒标记,就说明它未被修改过
 if(t[p].add){
     //所以进行一个总体修改
     t[p*2].pre += t[p].add*(t[p*2].r-t[p*2].l+1);
     t[p*2+1].pre += t[p].add*(t[p*2+1].r-t[p*2+1].l+1);
     //然后它被修改后,它的儿子节点肯定被覆盖的,所以直接把标记传给儿子就行了
     t[p*2].add+=t[p].add;
     t[p*2+1].add+=t[p].add;
     //清空自己
     t[p].add=0;
 }
}

查询部分,我们只需要去不断划分区间,把每个区间的和加起来就行,然后因为要查找,所以主要懒标记的问题,在拆分一个区间的时候首先要把懒标记下传再查询

long long ask(int p, long long x, long long y){
 if(x<=t[p].l && y>=t[p].r) return t[p].pre;
 spread(p);
 int mid = t[p].l+((t[p].r-t[p].l)>>1);
 long long ans=0;
 if(x<=mid) ans+=ask(p*2,x,y);
 if(y>mid)  ans+=ask(p*2+1,x,y);
 return ans;
}

注意的点:t 数组的上界是 \(2^{log(n)+1}\)

模板:P3373 【模板】线段树 2 P3372 【模板】线段树 1

线段树的题一定要记得开ll

posted @ 2025-11-04 11:11  Yuriha  阅读(0)  评论(0)    收藏  举报