线段树 (学习笔记)(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

浙公网安备 33010602011771号