问题 http://acm.hrbust.edu.cn/index.php?m=ProblemSet&a=showProblem&problem_id=1794;
线段树是一种二叉搜索树,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
对于线段树中的每一个非叶子节点[a,b],当a != b时,将它分为两部分,它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b],当a == b时,该点为叶子节点,不再分。
由于线段树的运行量比较大,对于*和/运算,都用移位来代替,能减少时间
一个数左移1位,相当于该数乘以2,一个数右移1位,相当于除以2
定义一个结构体来储存
struct Node{
int left, right; //区间[left, right]
int sum;//区间里的数的和
}
Node node[150000];
int a[50000], SUM;
在这里节点的值即为结构体数组的下标
由上图可知,节点I的左儿子的节点为I * 2, 右儿子节点为 I * 2 + 1, I的父亲节点为I/2;
知道这些关系后就能建树了
//对节点I进行建树
void buile(int left, int right, int i)
{
node[i].l = left;//I节点的左边界为left
node[i].r = right;//右边界为right
if(left == right)//当left == rigth时,说明已经是叶子节点,不再进行递归
{
node[i].sum = a[right];//当left == right时,为叶子节点,该区间([left, right])的和为a[left] 或a[right];
return ;//结束递归
}
build(left, (left + right) >> 1, i << 1); //当left != right 时,以区间[left, (left + right) / 2], 左儿子节点 i * 2 进行建树
build(((left + right) >> 1) + 1, right, (i << 1) + 1); //以区间[ (left + right) / 2 + 1, right], 右儿子节点 i * 2 + 1进行建树
node[i].sum = node[i << 1].sum + node[(i << 1) + 1].sum;//I节点的sum值为左儿子节点的sum + 右儿子节点的sum
}
//查询函数, 查询区间[left, right]的sum值
void query(int left, int right, int i)
{
if(left <= node[i].l && right >= node[i].r) //如果要查询的区间刚好覆盖节点I所对应的区间
SUM += node[i].sum; //则区间[left, right]的区间和为结点I所对应的区间的sum值
else
{
int mi = (node[i].l + node[i].r) >> 1;
if(left > mi) //如果left > (left + right) / 2, 说明要查询的区间在[mi,right]里,顺着结点I的右儿子进行查询
{
query(left, right, (i << 1) + 1);
}
else if(right <= mi)//如果right < (left + right) / 2, 则顺着结点I的左儿子进行查询
{
query(left, right, i << 1);
}
else
{//如果不是以上的情况,则说明所查询的区间被断开
query(left, right, i << 1);//顺着左儿子结点进行查询
query(left, right, (i << 1) + 1);//顺着右儿子结点进行查询
}
}
}
//点更改
void add(int x, int y, int i)
{
node[i].sum += y;//先把I区间的sum加上y;
if(node[i].left == x && node[i].right == x)//当递归到叶子节点时,结束递归
return ;
int mi = (node[i].l + node[i].r) >> 1;
if(x > mi) //如果x > mi,说明[mi, right]也增加了,因此把右儿子结点更新
{
add(x, y, (i << 1) + 1);
}
else
{
add(x, y, i << 1);柄 //否则更新左儿子结点
}
}
//同样,把y点改为x
void sub(int x, int y, int i)
{
node[i].sum -= y;
if(node[i].left == x && node[i].right == x)
return ;
int mi = (node[i].l + node[i].r) >> 1;
if(x > mi)
{
sub(x, y, (i << 1) + 1);
}
else
{
sub(x, y, i << 1);
}
}
完整代码:
#include <stdio.h>
#include <iostream>
#include <string.h>
using namespace std;
struct Node{
int l, r;
int sum;
};
Node node[200000];
int a[50005], SUM;
void build(int l, int r, int i)
{
node[i].l = l;
node[i].r = r;
if(l == r)
{
node[i].sum = a[r];
return ;
}
build(l, (l + r) >> 1, i << 1);
build(((l + r) >> 1) + 1, r, (i << 1) + 1);
node[i].sum = node[i << 1].sum + node[(i << 1) + 1].sum;
}
void query(int l, int r, int i)
{
if(l <= node[i].l && r >= node[i].r)
{
SUM += node[i].sum;
}
else
{
int mi = (node[i].l + node[i].r) >> 1;
if(l > mi)
{
query(l, r, (i << 1) + 1);
}
else if(r <= mi)
{
query(l, r, i << 1);
}
else
{
query(l, r, i << 1);
query(l, r, (i << 1) + 1);
}
}
}
void add(int x, int y, int i)
{
node[i].sum += y;
if(node[i].l == x && node[i].r == x)
return ;
int mi = (node[i].l + node[i].r) >> 1;
if(x > mi)
{
add(x, y, (i << 1) + 1);
}
else
{
add(x, y, i << 1);
}
}
void sub(int x, int y, int i)
{
node[i].sum -= y;
if(node[i].l == x && node[i].r == x)
return ;
int mi = (node[i].l + node[i].r) >> 1;
if(x > mi)
{
sub(x, y, (i << 1) + 1);
}
else
{
sub(x, y, i << 1);
}
}
int main(void)
{
int t;
scanf("%d", &t);
int Case = 1;
while(t--)
{
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++) //注意这里是从1到n
scanf("%d", &a[i]);
build(1, n, 1); ////以区间[1, n]建立线段树, 结点为1
char cmd[10];
printf("Case %d:\n", Case++);
while(scanf("%s", cmd) != EOF && strcmp("End", cmd) != 0)
{
if(strcmp("Query", cmd) == 0)
{
SUM = 0;
int l, r;
scanf("%d %d", &l, &r);
query(l, r, 1); //从第一个结点开始查询
printf("%d\n", SUM);
}
else if(strcmp("Add", cmd) == 0)
{
int x, y;
scanf("%d %d", &x, &y);
add(x, y, 1); //从第一个结点开始增加
}
else
{
int x, y;
scanf("%d %d", &x, &y);
sub(x, y, 1);
}
}
}
return 0;
}