题目大意:给出一个有n个数的数组,每次询问查询一个区间内的元素和,每次修改将区间内所有元素加上一个值
思路:构造线段树,将一个区间分成两半,再将每一部分分成两半,直到每一部分只剩一个元素,每个父结点记录所有子节点的权值和,在询问是,只需查找在区间内的父结点的值,在进行修改时,我们只对操作区间内的点做标记,不用真的改变所有值,如果以后需要再访问这个节点的子节点,就再把标记和值向下传递
#include<iostream>
#include<cstdio>
using namespace std;
int n, q;
const int N = 100005;
typedef long long gg;
gg tree[N * 4], tag[N * 4];
void build(int l, int r, int rt)//构造树状数组
{
tag[rt] = 0;//将所有点的标记初始化为0
if (l == r)//已经到了最底部
{
scanf_s("%lld", &tree[rt]);
return;
}
int mid = (l + r) >> 1;//将当前区间分成两部分
build(l, mid, rt << 1);//构建左侧的子树
build(mid + 1, r, rt << 1 | 1);//构建右侧的子树
tree[rt] = tree[rt << 1] + tree[rt << 1 | 1];//在回溯时获得父结点的值
}
void down(int rt, int len)
{//遇到了之前做过标记的点
if (tag[rt])
{
tag[rt << 1] += tag[rt];
tag[rt << 1 | 1] += tag[rt];//给两侧的子节点做上同样标记
tree[rt << 1] += (len - (len >> 1)) * tag[rt];
tree[rt << 1 | 1] += (len >> 1) * tag[rt];//将子节点更新
tag[rt] = 0;
}
}
void add(int a, int b, gg c, int l, int r, int rt)
{//a到b内每一个元素都+c
if (a <= l && b >= r)
{//当前区间已经包含在要查询的区间内
tree[rt] += (r - l + 1) * c;//该点的值相应增加
tag[rt] += c;//对该点做标记,并不去改变下面的值
return;
}
down(rt, r - l + 1);
int mid = (l + r) >> 1;
if (a <= mid)
{
add(a, b, c, l, mid, rt << 1);
}
if (b > mid)
{
add(a, b, c, mid + 1, r, rt << 1 | 1);
}
tree[rt] = tree[rt << 1] + tree[rt << 1 | 1];//回溯时改变父结点的值
}
long long query(int a, int b, int l, int r, int rt)
{//询问区间内元素和
if (a <= l && b >= r)//如果当前的区间在要查询的区间内,就加上这部分结果
return tree[rt];
down(rt, r - l + 1);
int mid = (l + r) >> 1;
gg ans = 0;
if (a <= mid)//如果要查询的区间左端点在中点的左边,就向左查找
ans += query(a, b, l, mid, rt << 1);
if (b > mid)//如果要查询的区间右端点在中点的右边,就向右查找
{
ans += query(a, b, mid + 1, r, rt << 1 | 1);
}
return ans;
}
int main()
{
cin >> n >> q;
build(1, n, 1);
while (q--)
{
char s[2];
cin >> s;
if (s[0] == 'Q')
{
int a, b;
scanf_s("%d%d", &a, &b);
printf("%lld\n", query(a, b, 1, n, 1));
}
else
{
int a, b;
gg c;
scanf_s("%d %d %lld", &a, &b, &c);
add(a, b, c, 1, n, 1);
}
}
return 0;
}