#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<vector>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
#define LL long long
#define inf 0x3f3f3f3f
#define lowb(x) x&-x
#define rep(i,j,k) for(int i=j;i<=k;i++)
#define per(i,j,k) for(int i=j;i>=k;i--)
#define N 100005
#define esp 1e-6
int M;
int t[2*N];
void build(int n)
{
for(M=1;M<n;M<<=1);//建树
for(int i=1;i<=n;i++)
scanf("%d",&t[M+i]);
per(i,M-2,1) t[i]=t[i<<1]+t[i<<1|1];
}
int query(int L,int R)
{
int ans=0;
int l=M+L-1;
int r=M+R+1;
for(;l^r^1;l>>=1,r>>=1)
{
if(~l&1)ans+=t[l^1];
if(r&1) ans+=t[r^1];
}
return ans;
}
void upd(int x,int k)
{
t[x+M]+=k;
x=(x+M)>>1;
for(;x;x>>=1) t[x]=t[x<<1]+t[x<<1|1];
}
int main()
{
int tt;
cin>>tt;
int cnt=0;
while(tt--)
{
memset(t,0,sizeof(t));
int n;
scanf("%d",&n);
build(n);
char s[6];
int i,j;
printf("Case %d:\n",++cnt);
while(~scanf("%s",s))
{
if(s[0]=='E')break;
scanf("%d%d",&i,&j);
if(s[0]=='A') upd(i,j);
if(s[0]=='S') upd(i,-j);
else if(s[0]=='Q')printf("%d\n",query(i,j));
}
}
}
刚刚学习了zkw线段树,感觉代码量很小,尤其是建树操作,只需要两行,(其实递归版的线段树也没有几行),虽然我目前只是学习了基本的思想,但是我还是想把自己的理解记录下来,以供以后查看。
需要注意的点
1. zkw线段树一般情况下需要的空间是2*n,最坏的情况下是4*n-1,因为我们把元素都存在了最后一层
而最后一层可以存2的n-1次方(n代表层数)个元素,即使少一个也要多开*2的空间
比如最后一层我们能存1024个元素 但是我们有1025个元素,那么不好意思
我们只能将数组的空间再扩大2倍。
2.元素的下标是从M+1开始存的(M是最小的大于n的2的幂次方)之所以从m+1开始存与它的查询方式有关。
他的查询方式是开区间查询如果要查询[l,r],就转化成查询[l-1,r+1];
例如对于本图圆圈中的数字代表第几个元素,上方的数字代表在数组中的位置。
现在要查询1-6 转化为查询转化后[M+l-1,M+r+1] 即转化为了[8-15];
对于两个兄弟节点来说,如果一个节点为左儿子,那么另一个节点为右儿子
所以说对于左端点来说,如果他是左儿子那么他的兄弟节点一定包含在区间内
对于右端点来说,如果他是右节点那么他的兄弟节点一定包含在查询区间内。
由此左右分别从下往上查询,并且不会查询到交集。查询停止的条件就是左右
端点成为了兄弟节点,即L^R^1=0,因为如果L与R相差1那么L^R=1,1^1=0;
另外需要注意的点就是单点更新的时候t[x+M]+=k;之后当前点已经更新了
需要往上移动了x=(x+M)>>1; 刚开始忘了写这一句,一直不知道bug出哪里。。
瞌睡。。碎觉。