二分思想,将一段区间不断细分处理,解决单点/区间的修改和查询问题
lazy标记是其中的精髓,可以大大提高区间修改的效率
#define maxn 100007
int A[maxn]; //原始数组,不一定要用
struct SegTreeNode{
int val; //节点值,如区间最大值
int lazy; //延迟更新标记
}SegTree[maxn<<2];
基本结构部分:val是节点的信息,可以是多个,可以是区间最大值、GCD等。
lazy是懒惰标记,区间更新时用
//构造根为rt,A区间为【l,r】线段树
void PushUp(int rt)
{
SegTree[rt].val = SegTree[rt<<1].val+SegTree[rt<<1|1].val; //决定了要统计说明GCD,max
}
void build(int l,int r,int rt)
{
SegTree[rt].lazy=0;
if(l==r)
{
SegTree[rt].val=A[l];
return;
}
int m=(l+r)/2;
build(l,m,rt*2);
build(m+1,r,rt*2+1);
PushUp(rt);
}
创建部分:PushUp函数用于回溯,可计算区间和或最大值等等(样例中给的是求最大值的代码)
build创建树,在基本结构省略left和right参数,则函数参数中都要加上。记得最后回溯
//A[L,R]+=C
void PushDown(int rt,int ln,int rn){
if(SegTree[rt].lazy){
SegTree[rt<<1].lazy+=SegTree[rt].lazy;
SegTree[rt<<1|1].lazy+=SegTree[rt].lazy;
SegTree[rt<<1].val+=SegTree[rt].lazy*ln;
SegTree[rt<<1|1].val+=SegTree[rt].lazy*rn;
SegTree[rt].lazy=0;
}
}
void Update(int L,int R,int C,int l,int r,int rt)
{
if(L<=l&&r<=R){
SegTree[rt].val+=C*(r-l+1);
SegTree[rt].lazy+=C;
}
int m=(l+r)>>1;
PushDown(rt,m-l+1,r-m); //若用了懒惰标记,需要下推
if(L<=m) Update(L,R,C,l,m,rt<<1);
if(R>m) Update(L,R,C,m+1,r,rt<<1|1);
PushUp(rt);
}
更新部分:
PushDown查看懒惰标记做下推操作
Update区间更新,启用懒惰标记版,当当前区间完全包括在要修改的区间内时标记后不再下推
int Query(int L,int R,int l,int r,int rt){
if(L<=l&&R>=r)
{
return SegTree[rt].val;
}
if(L>r||R<l)
{
return 0;
}
int m=(l+r)>>1;
PushDown(rt,m-l+1,r-m);
return Query(L,R,l,m,rt<<1)+Query(L,R,m+1,r,rt<<1|1);
}
查询:如果完全没有重合部分就返回零,这样对于大区间就可以直接返回两个子区间的结果和
记得下推
线段树经常要用到:一个问题只要能转化成对一些连续点的修改和统计问题,基本就可以用线段树解决 (优化),把模板记住最好