题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1166(这是一道模板得不能再板的题)
给出n个数,n<=10000000,和m个操作,每个操作可能有两种:1、在某个位置加上一个数;2、询问区间[l,r]的和,并输出。
显然这个题无法用前缀和,同样也无法使用O(n)的枚举。那我们就应该用一种强大的数据结构——线段树。
线段树支持快速的进行区间或单点的修改并求和。因为本蒟蒻时间有限就不再说了这里推荐一篇博客:http://www.cnblogs.com/TheRoadToTheGold/p/6254255.html
直接上代码(加注释的哦!)
#include<cstdio>
const int maxn = 50005;
int sum[maxn<<2];//数组要开四倍
void pushup(int rt)//使父亲节点加上子节点
{
sum[rt] = sum[rt<<1] + sum[rt<<1|1];
}
void build(int l , int r , int rt)//建树 l 表示左端点 r 表示右端点 rt表示当前是哪个节点
{
if(l == r)//如果是叶子节点
{
scanf("%d",&sum[rt]);
return;
}
int m = (l + r) >> 1;//二分
build(l , m , rt << 1);
build(m + 1 , r , rt << 1 | 1);
pushup(rt);//若是父亲节点就加上其子节点的值
}
void update(int p, int add, int l, int r, int rt)//单点加
{
if(l == r)//如果递归到叶子节点
{
sum[rt] += add;//加上add
return;
}
int m = (l + r) >> 1;
if(p <= m)//如果p在左子树
update(p , add , l , m , rt << 1);//在左子树上递归
else//如果是右子树
update(p , add , 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;
int ret = 0;
if(L <= m)//如果L在当前枚举到的区间中点左边
ret += query(L , R , l , m , rt << 1);
if(R > m)//如果R在当前枚举到的区间中点右边
ret += query(L , R , m + 1 , r , rt << 1 | 1);
return ret;
}
int main( )
{
int T , n;
scanf("%d",&T);//数据数
for (int i = 1 ; i <= T ; i ++)
{
printf("Case %d:\n",i);
scanf("%d",&n);//营地数也就是树的大小
build(1 , n , 1);//建树
char op[10];//存命令
while (scanf("%s",op))
{
if (op[0] == 'E') break;//因为命令的第一个字母都不一样我们大可只比较第一个字母
int a , b;
scanf("%d%d",&a,&b);
if (op[0] == 'Q') printf("%d\n",query(a , b , 1 , n , 1));
else if (op[0] == 'S') update(a , -b , 1 , n , 1);
else update(a , b , 1 , n , 1);
}
}
return 0;
}