将操作区间和点修改的位置和数字作为全局变量写起程序更加方便。
仅仅简单的单点更新不用使用下推函数和懒惰标记
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
const int maxn=50005;
int tree[maxn<<2];
int num[maxn];
int n;
char str[7];
int L,C,R;//点修改的位置和修改数
//L,R也表示操作区间
void pushup(int rt)
{
//pushup函数更新结点信息,这里是求和
//rt是线段树中的结点值
tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
void build_tree(int l,int r,int rt)
{
//l,r是区间端点,rt是映射在线段树中的结点
if(l==r)//表示建树到达底层
{
tree[rt]=num[l];
return;//到底层后函数返回
}
int m=(l+r)>>1;
build_tree(l,m,rt<<1);
build_tree(m+1,r,rt<<1|1);
pushup(rt);
}
void update(int l,int r,int rt)
{
if(l==r)
{
tree[rt]+=C;
//防止减成负数
if(tree[rt]<0)
tree[rt]=0;
return;
}
int m=(l+r)>>1;
if(L<=m)
update(l,m,rt<<1);
else
update(m+1,r,rt<<1|1);
pushup(rt);
}
//下推标记的函数
//如果只是单点修改,直接修改到底层即可,不用使用懒惰标记
int query(int l,int r,int rt)
{
if(L<=l&&r<=R)
return tree[rt];
int m=(l+r)>>1;
int ans=0;
if(L<=m)
ans+=query(l,m,rt<<1);
if(m<R)
ans+=query(m+1,r,rt<<1|1);
return ans;
}
int main()
{
int t;
scanf("%d",&t);
int T=t;
memset(num,0,sizeof(num));
while(t--)
{
scanf("%d",&n);
for(int i=1; i<=n; i++)
scanf("%d",&num[i]);
//建树
build_tree(1,n,1);
printf("Case %d:\n",T-t);
while(scanf("%s",str))
{
if(str[0]=='E')
break;
else if(str[0]=='Q')
{
//线段树查询
scanf("%d%d",&L,&R);
printf("%d\n",query(1,n,1));
}
else if(str[0]=='A')
{
//线段树点修改增加
scanf("%d%d",&L,&C);
update(1,n,1);
}
else if(str[0]=='S')
{
//线段树点修改减少
scanf("%d%d",&L,&C);
C*=-1;
update(1,n,1);
}
}
}
return 0;
}
查询语句中的几条语句有些难理解,需要好好琢磨,如下所示:
int ans=0;
if(L<=m)
ans+=query(l,m,rt<<1);
if(m<R)
ans+=query(m+1,r,rt<<1|1);
return ans;
这里语句的含义是判断左右子树和[L,R]这个操作区间有没有交集,有交集就进行递归。
左子树的区间是[l,m],判断条件是判定m是否大于L。
右子树的区间是[m+1,r],判断条件是判定m是否小于R。
我们初始的[l,r]取值足够合适,不会出现l>R(越界)的情况?
证明:【向前考虑条件:】
假设l>R,则应当来源于递归时传入参数的上述代码的两种情况。
首先判定传参的第一种情况:L<=m时,此时的l应当是传参之前的l,一直递归向上考虑,如果一直是第一种传参情况,l初始值为1,不可能发生l>R的情况。
其次判定传参的第二种情况:m<R时,此时的l应当是传参之前的m+1,由于限制条件是m<R,则传参之前的m+1==传参之后的l,此时的l的最大值为(m)max+1==R,不会出现l>R的情况。
【向后考虑影响:】
如前所述,向后传参时如果一直是第一种情况,那么不会发生l>R的越界问题;
如果出现第二种传参时,传参后区间为[l,r],m=(l+r)>>1,
若m==l,则出现遍历到底层应当返回函数值的情况,
若m>l,此时m>R,所以二层传参时不会出现第二种传参情况了,只会出现第一种,则l一直不变,直到出现m==l为止,
证明完毕。
r<L越界的情况证明相似。
结论,我们的代码最终总会将[l,r]这个当前区间控制在操作区间[L,R]之间,而不会出现r<L或者l>R这两种越界的情况。