线段树是对区间进行查询和维护的一种数据结构
对于区间操作比树状数组更加通用
缺点就是又臭又长。。
建树:
void build(int p,int l,int r){ if(l==r){ /*something*/ return; } int mid=(l+r)>>1; build(p<<1,l,mid); build(p<<1|1,mid+1,r); updata(p); }
updata(p)中是要维护的数据
如区间和&最值:
void updata(int p){ sum[p]=sum[p<<1]+sum[p<<1|1]; mx[p]=max(mx[p<<1],mx[p<<1|1]); mn[p]=min(mn[p<<1],mn[p<<1|1]); }
将第x个数改成v,也就是单点修改
单点修改:
void change(int p,int l,int r,int x,int val){ if(l==r){ /*something*/ return; } int mid=(l+r)>>1; if(x<=mid)change(p<<1,l,mid,x,val); else change(p<<1|1,mid+1,r,x,val); updata(p); }
查询 l~r 的信息,如区间和
区间查询:
int ask(int p,int l,int r,int x,int y){ if(l>=x&&r<=y)return sum[p]; int mid=(l+r)>>1,val=0; if(x<=mid)val+=ask(p<<1,l,mid,x,y); if(y>mid)val+=ask(p<<1|1,mid+1,r,x,y); return val; }
修改 l~r的信息,如区间和
如果递归逐一更新修改,时间复杂度为 $O(n)$。
要用一个叫延迟标记的东西
延迟标记:
void pushdown(int p,int l,int r){ if(!tag[p])return; int mid=(l+r)>>1; tag[p<<1]+=tag[p]; tag[p<<1|1]+=tag[p]; sum[p<<1]+=tag[p]*(mid-l+1); sum[p<<1|1]+=tag[p]*(r-mid); tag[p]=0; }
区间修改:
void change(int p,int l,int r,int x,int y,int k){ if(l>=x&&r<=y){ tag[p]+=k; sum[p]+=k*(r-l+1); return; } pushdown(p,l,r); int mid=(l+r)>>1; if(x<=mid)change(p<<1|1,l,mid,x,y,k); if(y>mid)change(p<<1|1,mid+1,r,x,y,k); updata(p); }
在查询的时候,向下递归时就下传延迟标记
区间查询:
int ask(int p,int l,int r,int x,int y){ if(l>=x&&r<=y)return sum[p]; pushdown(p,l,r); int mid=(l+r)>>1,val=0; if(x<=mid)val+=ask(p<<1,l,mid,x,y); if(y>mid)val+=ask(p<<1|1,mid+1,r,x,y); return val; }
以上就是线段树最基础的操作了
模板题:
https://www.luogu.com.cn/problem/P3372
https://www.luogu.com.cn/problem/P3373
https://www.luogu.com.cn/problem/P2357
蒟蒻刚学一周
以后会继续补充的。。