线段树
线段树-概念
树上每个点对应序列上一段连续的区间
根节点:(1,n)
左儿子:(1,n/2)
右儿子:(n/2)
………………
叶子节点:对应一个点
层数:logn
共1+2+4+……+n=2n个节点
在节点上维护对应区间的信息
线段树-功能
首先我们需要一个define:
#define ls(k<<1) #define rs(k<<1|1) #define mid(l+r>>1)
1.单点加
将某一个数加v
x对应的叶节点+v
依次向上更新x祖先的信息
void pushup(int k,int l,int r){ s[k]=s[ls]+s[rs]; //左右儿子更新该节点 } void plus(int k,int l,int r,int x,int v){ //将x位置点加v if(l==r) return s[l]+=v,void(); if(x<=mid) plus(ls,l,mid,k,v); //只找x所在区间 else plus(rs,mid+1,r,k,v); pushup(k,l,r); }
2.区间求和
s[k]:k对应的区间和
s[k] = s[ls]+s[rs]
边界:当x为叶节点时s[k] = a[l]
按照二叉树形式从上到下从左到右依次给线段树上的节点编号
建树O(n):
void build(int k,int l,int r) { if(l == r) return s[k]=a[l],void(); build(ls,l,mid); build(rs,mid+1,r); return s[k]=s[ls]+s[rs],void(); }
考虑当前节点k对应[l,r]与[x,y]关系
若[x,y]包含[l,r]计入;
否则若[x,y]包含[l,mid],递归ls,
若[x,y]包含[mid+1,r],递归rs
很显然:
int ask(int k,int l,int r,int x,int y){ if(x <= l && r <= y) return s[k]; int res = 0; if(x <= mid) res+=ask(ls,l,mid,x,y); if(y > mid) res+=ask(rs,mid+1,r,x,y); return res; }
3.区间修改,区间求和--------懒标记
lazy[k]代表将k对应的区间整体加lazy[k].
当我们需要k对应区间加v时,选择lazy[k]加v代表这段加过v了(懒)
像这样↓
void ladd(int k,int l,int r,int v){ s[k]+=(r-l+1)*v;lazy[k]+=v; //自己要先加好,后代懒得加,打个标记 return; }
当我们查询到有懒标记点k时,启动懒标记干活(将懒标记影响下传到子树中,并将懒标记清空)
void pushdown(int k,int l,int r){ ladd(ls,l,mid,lazy[k]); //给左儿子 ladd(rs,mid+1,r,lazy[k]); //给右儿子 lazy[k]=0; }
pushdown()需要在查询时同步进行!
于是,就可以得新的区间加法代码:
void plu(int k,int l,int r,int x,int y,int v){ //[x,y]区间加v if(x<=l&&r<=y) return add(k,l,r,v); //若k对应的区间被x,y包含,打上懒标记就走 pushdown(k,l,r); if(x <= mid) plu(ls,l,mid,x,y,v); if(y > mid) plu(rs,mid+1,r,x,y,v); return; }
区间求和代码:
int ask(int k,int l,int r,int x,int y){ if(x <= l && r <= y) return s[k]; pushdown(k,l,r); //唯一不同 int res = 0; if(x <= mid) res+=ask(ls,l,mid,x,y); if(y > mid) res+=ask(rs,mid+1,r,x,y);
pushup(k,l,r); return res; }
3.++区间乘?
开两个lazy数组,先乘再加
浙公网安备 33010602011771号