题目链接:http://wikioi.com/problem/1082/
算法与思路:线段树
对于线段树的初学者,可以参考以下链接,里面有相关的习题和详解
http://www.notonlysuccess.com/index.php/segment-tree-complete/
题目意思很直白,区间更新区间求和,思路倒没什么多讲的,关键在理解代码。
值得注意的一个地方是,与单点更新不同,区间更新要用到懒惰标记(lazy-tag),
具体的实现是在更新区间的时候并不对区间内每个点都进行修改,仅对当前父节点进行标记,
表示该父节点管辖的区间需更新,待到查询的时候才由该父节点向下更新,这样才能使得线段树的复杂度为O(log n)。
另外这个模版同样适用于区间更新,求单点大小的题目,只需要把query函数中代表查询区间的[a,b]改成[a,a]即可,
是不是很神奇的说~~
#include<stdio.h>
#define maxn 200100
#define lson l, mid, root<<1
#define rson mid+1, r, root<<1|1
long long sum[maxn<<2], tag[maxn<<2];
void Pushup(int root) //向上传递sum值
{
sum[root] = sum[root<<1] + sum[root<<1|1];
}
void Pushdown(int root, int num) //向下修改,更新左右孩子
{
tag[root<<1] += tag[root];
tag[root<<1|1] += tag[root]; //懒惰标记
sum[root<<1] += (num - (num>>1)) * tag[root]; //左孩子的区间长度大于等于右孩子
sum[root<<1|1] += (num>>1) * tag[root];
tag[root] = 0; //修改后,把延迟标记置0
}
void build(int l, int r, int root) //建树
{
tag[root] = 0; //利用建树的过程将tag初始化
if(l == r)
{
scanf("%lld", &sum[root]);
return ;
}
int mid = (l + r)>>1;
build(lson); //建左子树
build(rson); //建右子树
Pushup(root); //向上更新sum值
}
void update(int L, int R, int x, int l, int r, int root) //修改区间的值
{
if(L <= l && r <= R)
{
tag[root] += x; //修改的增量是x
sum[root] += (long long)(r - l + 1) * x; //区间长度为(r-l+1),每个元素都要加x
return;
}
Pushdown(root, r - l + 1); //向下修改
int mid = (l + r) >> 1;
if(L <= mid) update(L, R, x, lson);
if(R > mid) update(L, R, x, rson);
Pushup(root); //修改完,向上传递sum
}
long long query(int L, int R, int l, int r, int root) //查询区间的sum
{
if(L <= l && r <= R)
return sum[root];
Pushdown(root, r - l + 1); //向下边修改边查询
long long res = 0;
int mid = (l + r) >> 1;
if(L <= mid) res += query(L, R, lson);
if(R > mid) res += query(L, R, rson);
return res;
}
int main()
{
int n, q, op, a, b, x;
while(~scanf("%d", &n))
{
build(1, n, 1);
scanf("%d", &q);
while(q--)
{
scanf("%d", &op);
if(op == 1)
{
scanf("%d %d %d",&a, &b, &x);
update(a, b, x, 1, n ,1);
}
else
{
scanf("%d %d",&a, &b);
printf("%lld\n", query(a, b, 1, n, 1));
}
}
}
return 0;
}