题目描述
如题,已知一个数列,你需要进行下面两种操作:
将某区间每一个数加上 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% 的数据:n≤8,m≤10。
对于 70% 的数据:n≤10^3 ,m≤10^
对于 100% 的数据:1≤n,m≤10^5。
保证任意时刻数列中任意元素的和在[ −2^63 ,2^63) 内。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
int n,m;
const int mm = 100010;
int a[100010];
struct Tree{
int l,r;
ll sum,lazy; // sum代表该节点维护的值,l,r代表该节点维护的区间范围 至于lazy涉及到一个叫懒标记的东西
}tree[4*mm+5];
void build_tree(int id,int l,int r){ // 建树
tree[id].l = l;
tree[id].r = r;
if(l == r ){
tree[id].sum = a[l];
return;
}
int mid = (l + r) >> 1;
build_tree(id*2,l,mid);
build_tree(id*2+1,mid+1,r);
tree[id].sum = tree[id*2].sum + tree[id*2+1].sum;
}
void check(int id){
tree[id*2].lazy += tree[id].lazy;
tree[id*2].sum += (tree[id*2].r-tree[id*2].l+1 )*tree[id].lazy ;
tree[id*2+1].lazy += tree[id].lazy;
tree[id*2+1].sum += (tree[id*2+1].r-tree[id*2+1].l+1 )*tree[id].lazy;
tree[id].lazy = 0;
}
void update(int id, int l, int r, int v){ // 更新
if(tree[id].l > r || tree[id].r < l ) return;
if(tree[id].l >= l && tree[id].r <= r ){
tree[id].lazy += v;
tree[id].sum += (tree[id].r - tree[id].l + 1 ) * v;
return;
}
if(tree[id].lazy > 0 ) check(id);
update(id*2,l, r, v );
update(id*2+1, l ,r, v);
tree[id].sum = tree[id*2].sum + tree[id*2+1].sum;
}
ll query(int id, int l, int r){ // 查询
if(tree[id].l > r || tree[id].r < l ) return 0;
if(tree[id].l >= l && tree[id].r <= r ) return tree[id].sum;
if(tree[id].lazy > 0 ) check(id);
return query(id*2,l,r) + query(id*2+1,l,r);
}
int main() {
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; i++) {
scanf("%d",&a[i] );
}
build_tree(1,1,n);
for(int i = 1; i <= m; i++) {
int x,y,z,v;
scanf("%d",&z);
if(z == 1) {
scanf("%d%d%d",&x,&y,&v);
update(1,x,y,v);
} else {
scanf("%d%d",&x,&y);
printf("%lld\n",query(1,x,y));
}
}
return 0;
}
现在我们思考一个问题,如果我们要对这颗树上的区间加上v,是不是只用找到代表这个区间的节点(可能不止一个节点,例如[2,3]这个区间就需要查找[2,2]和[3,3]这两个区间,然后将他们的sum加起来),然后将这个节点的sum值加上v乘以这个区间所包含的数的个数?下次访问相同的区间的时候直接将这个数返回过去,根本就不用再向下遍历了。
但是问题又来了,上一次我对[1,2]这个区间加了v,这一次我想知道[2,3]的区间和怎么办呢?要是只用我们上面提到的方法,很明显遍历到[2,2]的时候这个点并没有加v,那返回时答案就错了呀!
怎么办?
这时延迟标记(lazy)就派上用场了,它代表我对这个区间的数加过的值,我们在对一个区间加了v之后,我们将lazy的值也加上这个v,后来我们再访问这个区间但只是访问这个区间的一部分时我们就可以知道这个父节点下面的儿子节点需要加上多少了,但我们现在不用急着将这个lazy标记给它的儿子们加上,因为这很费时,当我们下次将要访问它的儿子节点时再给他们加上lazy标记,此时这个父节点的lazy值就要清零,我们就称其为标记下传。
举个例子:我们在访问[2,3]时,我们会访问[2,2]和[3,3]这两个节点,将要访问[2,2]时,我们肯定会访问[1,2]这个点,那么在向下遍历的同时,我们就顺便将lazy标记下传,下传时就把每个叶子节点的lazy加上这个值,再按之前说到的方法更新sum。如此反复。
区间更新
考虑将一个区间加上一个数,我们可以从根节点不断向下查找,当发现我们要修改的区间覆盖了当前节点时,我们就把这个区间给修改,并打上懒标记(由于懒标记存在,我们就不必再修改他的儿子节点),否则下传懒标记,继续向下找
区间查询
考虑询问一个区间的和,依旧是从根节点向下查找,当发现该节点被覆盖时,就返回维护的值,否则下传懒标记,查询左右儿子,累加答案
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
int n,m,mod;
const int mm = 100010;
int a[100010];
struct Tree{
ll l,r;
ll sum,add,mul; // sum代表该节点维护的值,l,r代表该节点维护的区间范围 至于add和mul涉及到一个叫懒标记的东西
}tree[4*mm+5];
void build_tree(ll id,ll l,ll r){ // 建树
tree[id].l = l;
tree[id].r = r;
tree[id].mul = 1;
if(l == r ){
tree[id].sum = a[l]%mod ;
return;
}
int mid = (l + r) >> 1;
build_tree(id*2,l,mid);
build_tree(id*2+1,mid+1,r);
tree[id].sum = (tree[id*2].sum + tree[id*2+1].sum)%mod;
}
void check(ll id){ // 修改懒标记
tree[id*2].sum = (tree[id].mul * tree[id*2].sum+(tree[id].add*(tree[id*2].r - tree[id*2].l + 1 ))%mod)%mod ;
tree[id*2+1].sum = (tree[id].mul * tree[id*2+1].sum+(tree[id].add*(tree[id*2+1].r - tree[id*2+1].l + 1 ))%mod)%mod ;
tree[id*2].mul = (tree[id].mul*tree[id*2].mul)%mod;
tree[id*2+1].mul = (tree[id].mul*tree[id*2+1].mul)%mod;
tree[id*2].add = (tree[id*2].add*tree[id].mul+tree[id].add )%mod;
tree[id*2+1].add = (tree[id*2+1].add*tree[id].mul+tree[id].add )%mod;
tree[id].add = 0;
tree[id].mul = 1;
}
void update_add(ll id, ll l, ll r, ll v){ // 相除
if(tree[id].l > r || tree[id].r < l ) return;
if(tree[id].l >= l && tree[id].r <= r ){
tree[id].add = (tree[id].add + v) % mod;
tree[id].sum = (tree[id].sum + (tree[id].r - tree[id].l + 1 ) * v) % mod;
return;
}
/* if(tree[id].add > 0 || tree[id].mul > 1 ) */ check(id);
update_add(id*2,l, r, v );
update_add(id*2+1, l ,r, v);
tree[id].sum = (tree[id*2].sum + tree[id*2+1].sum)%mod;
}
void update_mul(ll id, ll l,ll r,ll v){ // 相乘
if(tree[id].l > r || tree[id].r < l ) return;
if(tree[id].l >= l && tree[id].r <= r ){
tree[id].add = (tree[id].add * v ) % mod;
tree[id].mul = (tree[id].mul * v ) % mod;
tree[id].sum = (tree[id].sum * v) % mod;
return;
}
/* if(tree[id].add > 0 || tree[id].mul > 1) */check(id);
update_mul(id*2,l, r, v );
update_mul(id*2+1, l ,r, v);
tree[id].sum = (tree[id*2].sum + tree[id*2+1].sum) % mod;
}
ll query(ll id, ll l,ll r){// 查询
if(tree[id].l > r || tree[id].r < l ) return 0;
if(tree[id].l >= l && tree[id].r <= r ) return tree[id].sum%mod;
/* if(tree[id].add > 0 || tree[id].mul > 1 ) */ check(id);
return (query(id*2,l,r) + query(id*2+1,l,r))%mod;
}
int main() {
scanf("%d%d%d",&n,&m,&mod);
for(int i = 1; i <= n; i++) {
scanf("%d",&a[i] );
}
build_tree(1,1,n);
for(int i = 1; i <= m; i++) {
int x,y,z,v;
scanf("%d",&z);
if(z == 1) {
scanf("%d%d%d",&x,&y,&v);
update_mul(1,x,y,v);
} else if( z == 2) {
scanf("%d%d%d",&x,&y,&v);
update_add(1,x,y,v);
} else {
scanf("%d%d",&x,&y);
printf("%lld\n",query(1,x,y));
}
}
return 0;
}