题目描述
如题,已知一个数列,你需要进行下面两种操作:
- 将某区间每一个数加上 kk。
- 求出某区间每一个数的和。
输入格式
第一行包含两个整数 n, mn,m,分别表示该数列数字的个数和操作的总个数。
第二行包含 nn 个用空格分隔的整数,其中第 ii 个数字表示数列第 ii 项的初始值。
接下来 mm 行每行包含 33 或 44 个整数,表示一个操作,具体如下:
1 x y k
:将区间 [x, y][x,y] 内每个数加上 kk。2 x y
:输出区间 [x, y][x,y] 内每个数的和。
输出格式
输出包含若干行整数,即为所有操作 2 的结果。
输入输出样例
输入 #1复制
5 5 1 5 4 2 3 2 2 4 1 2 3 2 2 3 4 1 1 5 1 2 1 4
输出 #1复制
11 8 20
说明/提示
对于 30\%30% 的数据:n \le 8n≤8,m \le 10m≤10。
对于 70\%70% 的数据:n \le {10}^3n≤103,m \le {10}^4m≤104。
对于 100\%100% 的数据:1 \le n, m \le {10}^51≤n,m≤105。
保证任意时刻数列中所有元素的绝对值之和 \le {10}^{18}≤1018。
【样例解释】
//洛谷3372,线段树,区间修改+区间查询
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e5 + 10;
ll a[N]; //记录数列的元素,从a[1]开始
ll tree[N << 2]; //tree[i]为第i个节点的值,表示一个线段区间的值,如最值、区间和
ll tag[N << 2]; //tree[i]为第i个节点的lazy-Tag,统一记录这个区间的修改
ll ls(ll p){
return p << 1; //定位做儿子:p*2
}
ll rs(ll p){
return p << 1 | 1; //定位右儿子:p*2+1
}
void push_up(ll p){ //从下向上传递区间值
tree[p] = tree[ls(p)] + tree[rs(p)];
//本题是求区间和,如果是求最小值,改为tree[p] = min(tree[ls(p)],tree[rs(p)]);
}
void build(ll p,ll pl,ll pr){ //建树。p为节点编号,它指向区间[pl,pr]
tag[p] = 0; //lazy-Tag标记
if(pl == pr){ //最底层的叶子,赋值
tree[p] = a[pl];
return ;
}
ll mid = (pl + pr) >> 1; //分治:折半
build(ls(p),pl,mid); //左儿子
build(rs(p),mid+1,pr); //右儿子
push_up(p); //从下往上传递区间值
}
void addtag(ll p,ll pl,ll pr,ll d){ //给节点p打tag标记,并更新tree
tag[p] += d; //打上tag标记
tree[p] += d * (pr - pl + 1); //计算新的tree
}
void push_down(ll p,ll pl,ll pr){ //不能覆盖时,把tag传给子树
if(tag[p]){ //有tag标记,这是以前做区间修改时留下的
ll mid = (pl + pr)>> 1;
addtag(ls(p),pl,mid,tag[p]);//把tag标记传给左子树
addtag(rs(p),mid+1,pr,tag[p]);//把tag标记传给右子树
tag[p] = 0; //p自己的tag被传走了,归零
}
}
void update(ll L,ll R,ll p,ll pl,ll pr,ll d){//区间修改:[L,R]内每个元素加d
if(L <= pl&&pr <= R){ //完全覆盖,直接返回这个节点,它的子树不用再深入了
addtag(p,pl,pr,d); //给节点p打tag标记,下一次区间修改到p时会用到
return;
}
push_down(p,pl,pr); //如果不能覆盖,把tag传给子树
ll mid = (pl + pr) >> 1;
if(L <= mid) update(L,R,ls(p),pl,mid,d); //递归左子树
if(R > mid) update(L,R,rs(p),mid+1,pr,d); //递归右子树
push_up(p); //更新
}
ll query(ll L,ll R,ll p,ll pl,ll pr){
//查询区间[L,R],p是当前节点(线段)的编号,[pl,pr]是节点p表示的线段区间
if(pl >= L&&R >= pr) return tree[p]; //完全覆盖,直接返回
push_down(p,pl,pr); //不能覆盖,递归子树
ll res = 0;
ll mid = (pl + pr) >> 1;
if(L <= mid) res += query(L,R,ls(p),pl,mid);//左子节点有重叠
if(R > mid) res += query(L,R,rs(p),mid+1,pr);//右子节点有重叠
return res;
}
int main(){
ll n,m;
scanf("%lld%lld",&n,&m);
for(ll i = 1;i <= n;i++) scanf("%lld",&a[i]);
build(1,1,n); //建树
while(m--){
ll q,L,R,d;
scanf("%lld",&q);
if(q == 1){ //区间修改:[L,R]的每个元素加d
scanf("%lld%lld%lld",&L,&R,&d);
update(L,R,1,1,n,d);
}
else{ //区间查询:[L,R]区间和
scanf("%lld%lld",&L,&R);
printf("%lld\n",query(L,R,1,1,n));
}
}
return 0;
}