线段树代码中如何实现?
(0)定义:
- #define maxn 100007 //元素总个数
- int Sum[maxn<<2],Add[maxn<<2];//Sum求和,Add为懒惰标记 (开四倍空间)
- int A[maxn],n;//存原数组下标[1,n]
(1)建树:
- //PushUp函数更新节点信息,这里是求和
- void PushUp(int rt){Sum[rt]=Sum[rt<<1]+Sum[rt<<1|1];}
- //Build函数建立线段树
- void Build(int l,int r,int rt){ //[l,r]表示当前节点区间,rt表示当前节点的实际存储位置
- if(l==r) {//若到达叶节点
- Sum[rt]=A[l];//存储A数组的值
- return;
- }
- int m=(l+r)>>1;
- //左右递归
- Build(l,m,rt<<1);
- Build(m+1,r,rt<<1|1);
- //更新信息
- PushUp(rt);
- }
(2)点修改:
假设A[L]+=C:
- void Update(int L,int C,int l,int r,int rt){//[l,r]表示当前区间,rt是当前节点编号//l,r表示当前节点区间,rt表示当前节点编号
- if(l==r){//到达叶节点,修改叶节点的值
- Sum[rt]+=C;
- return;
- }
- int m=(l+r)>>1;
- //根据条件判断往左子树调用还是往右
- if(L <= m) Update(L,C,l,m,rt<<1);
- else Update(L,C,m+1,r,rt<<1|1);
- PushUp(rt);//子节点的信息更新了,所以本节点也要更新信息
- }
点修改其实可以写的更简单,只需要把一路经过的Sum都+=C就行了,不过上面的代码更加规范,在题目更加复杂的时候,按照格式写更不容易错。
(3)区间修改:
假设A[L,R]+=C
首先是下推标记的函数:
- void PushDown(int rt,int ln,int rn){
- //ln,rn为左子树,右子树的数字数量。
- if(Add[rt]){
- //下推标记
- Add[rt<<1]+=Add[rt];
- Add[rt<<1|1]+=Add[rt];
- //修改子节点的Sum使之与对应的Add相对应
- Sum[rt<<1]+=Add[rt]*ln;
- Sum[rt<<1|1]+=Add[rt]*rn;
- //清除本节点标记
- Add[rt]=0;
- }
- }
更新函数:
- void Update(int L,int R,int C,int l,int r,int rt){//L,R表示操作区间,l,r表示当前节点区间,rt表示当前节点编号
- if(L <= l && r <= R){//如果本区间完全在操作区间[L,R]以内
- Sum[rt]+=C*(r-l+1);//更新数字和,向上保持正确
- Add[rt]+=C;//增加Add标记,表示本区间的Sum正确,子区间的Sum仍需要根据Add的值来调整
- return ;
- }
- int m=(l+r)>>1;
- PushDown(rt,m-l+1,r-m);//下推标记
- //这里判断左右子树跟[L,R]有无交集,有交集才递归
- 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);//更新本节点信息
- }
(4)区间查询(以求和为例):
询问A[L..R]的和
注意到,整个函数的递归过程中,L,R是不变的。
首先如果当前区间[l,r]在[L,R]内部,就直接累加答案
如果左子区间与[L,R]有重叠,就递归左子树,右子树同理。
- int Query(int L,int R,int l,int r,int rt){//[L,R]表示操作区间,[l,r]表示当前区间,rt:当前节点编号
- if(L <= l && r <= R){
- //在区间内直接返回
- return Sum[rt];
- }
- int m=(l+r)>>1;
- //左子区间:[l,m] 右子区间:[m+1,r] 求和区间:[L,R]
- //下推标记,因为之前下面有些结点的值没有被修改(只有点修改的话,这句不需要)
- PushDown(rt,m-l+1,r-m);
- //累加答案
- int ANS=0;
- if(L <= m) ANS+=Query(L,R,l,m,rt<<1);//左子区间与[L,R]有重叠,递归
- if(R > m) ANS+=Query(L,R,m+1,r,rt<<1|1); //右子区间与[L,R]有重叠,递归
- return ANS;
- }
把上面代码综合一下:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define maxn 10000
using namespace std;
int sum[maxn<<2],add[maxn<<2];
int a[maxn],n;
void pushup(int rt) //更新节点
{
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void build(int l,int r,int rt)//建树
{
if(l==r)
{
sum[rt]=a[l];
return ;
}
int m=(l+r)>>1;
build(l,m,rt<<1);
build(m+1,r,rt<<1|1);
pushup(rt);
}
void pushdown(int rt,int ln,int rn) //下推标记
{ //ln,rn为左子树,右子树的数字数量
if(add[rt])
{
add[rt<<1]+=add[rt];
add[rt<<1|1]+=add[rt];
sum[rt<<1]+=add[rt]*ln;
sum[rt<<1|1]+=add[rt]*rn;
add[rt]=0;
}
}
void update(int L,int R,int c,int l,int r,int rt) //L,R表示操作区间,l,r表示当前节点区间,rt表示当前节点编号
{
if(L<=l&&r<=R)
{
sum[rt]+=c*(r-l+1);
add[rt]+=c;
return ;
}
int m=(l+r)>>1;
pushdown(rt,m-l+1,r-m);
if(L<=m)
update(L,R,c,l,m,rt<<1);
if(m<R)
update(L,R,c,m+1,r,rt<<1|1);
pushup(rt);
}
int query(int L,int R,int l,int r,int rt)
{
if(L<=l&&r<=R)
return sum[rt];
int m=(l+r)>>1;
pushdown(rt,m-l+1,r-m);
int ans=0;
if(L<=m)
ans+=query(L,R,l,m,rt<<1);
if(m<R)
ans+=query(L,R,m+1,r,rt<<1|1);
return ans;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
memset(sum,0,sizeof(sum));
memset(add,0,sizeof(add));
build(1,n,1);
printf("%d\n",query(2,3,1,n,1));
update(2,4,4,1,n,1);
printf("%d",query(1,4,1,n,1));
return 0;
}
区间连续0问题:
- #define maxn 200001
- using namespace std;
- int L[maxn<<2][2];//从左开始连续零个数
- int R[maxn<<2][2];//从右
- int Max[maxn<<2][2];//区间最大连续零
- bool Pure[maxn<<2][2];//是否全零
- int M[2];
- void PushUp(int rt,int k){//更新rt节点的四个数据 k来选择两棵线段树
- Pure[rt][k]=Pure[rt<<1][k]&&Pure[rt<<1|1][k];
- Max[rt][k]=max(R[rt<<1][k]+L[rt<<1|1][k],max(Max[rt<<1][k],Max[rt<<1|1][k]));
- L[rt][k]=Pure[rt<<1][k]?L[rt<<1][k]+L[rt<<1|1][k]:L[rt<<1][k];
- R[rt][k]=Pure[rt<<1|1][k]?R[rt<<1|1][k]+R[rt<<1][k]:R[rt<<1|1][k];
- }