原题:洛谷:https://www.luogu.org/problem/show?pid=3372
题目描述
如题,已知一个数列,你需要进行下面两种操作:
1.将某区间每一个数加上x
2.求出某区间每一个数的和
输入输出格式
输入格式:
第一行包含两个整数N、M,分别表示该数列数字的个数和操作的总个数。
第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。
接下来M行每行包含3或4个整数,表示一个操作,具体如下:
操作1: 格式:1 x y k 含义:将区间[x,y]内每个数加上k
操作2: 格式:2 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
说明
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=8,M<=10
对于70%的数据:N<=1000,M<=10000
对于100%的数据:N<=100000,M<=100000
(数据已经过加强^_^,保证在int64/long long数据范围内
代码解析已经在代码中呈现!!!
//线段树,初学者适用
//与二叉堆性质相似a的左儿子a[2*i]右儿子a[2*i+1],线
//段树是将每个区间[l,r]分解成[l,m],[m+1,r],m=(l+r)/2
//直到l==r
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define maxn 1000000
using namespace std;
int x,y,k;
long long add[maxn]/*表示每个点的标记(惰性标记)*/;
long long a[maxn];//存储最初的数据
struct node{
int left,right;
long long sum;//记录该条线段出现的次数
}tree[maxn];//保存每个点左右端点和极值
//数组模拟,空间开到4倍防止访问越界
void buildtree(int i,int l,int r){//构造线段树
tree[i].left=l;
tree[i].right=r;
if(l==r){//找到叶子节点并且赋值
tree[i].sum=a[l];
return;
}//如果该点是叶节点
int mid=(l+r)>>1;
buildtree(i<<1,l,mid);//左移一位(二进制) //左子树
buildtree((i<<1)|1,mid+1,r);//右子树
//按位或eg:3|2=0011|0010=0011
tree[i].sum=tree[i<<1].sum+tree[(i<<1)+1].sum;
//回溯维护区间和
}
void pushdown(int i){//下放惰性标记
int lc=i<<1;;
int rc=(i<<1)+1;
tree[lc].sum+=(tree[lc].right-tree[lc].left+1)*add[i];
tree[rc].sum+=(tree[rc].right-tree[rc].left+1)*add[i];
add[lc]+=add[i];
add[rc]+=add[i];
add[i]=0;//此处下放标记
}
void update(int i,int x,int y,long long k){//区间修改
int lc=i<<1;
int rc=(i<<1)|1;
if(tree[i].left>y || tree[i].right<x){
return;//如果此区间完全无关
}
if(x<=tree[i].left && tree[i].right<=y){
tree[i].sum+=(tree[i].right-tree[i].left+1)*k;
//加至此处就不继续往下加
add[i]+=k;
//存放惰性标记
}
//如果此处是完全有关区间
else{
if(add[i]){
pushdown(i);//下放惰性标记
}
update(lc,x,y,k);
update(rc,x,y,k);
tree[i].sum=tree[lc].sum+tree[rc].sum;
}//二分添加
}
long long find(int i,int x,int y){
int lc=i<<1;
int rc=(i<<1)+1;
if(x<=tree[i].left && tree[i].right<=y){
return tree[i].sum;
}
//当前节点的区间完全被目标区间包含
if(tree[i].left>y || tree[i].right<x){
return 0;
}
//完全在左子树中or右子树
if(add[i]){
pushdown(i);//下放惰性标记
}
return find(lc,x,y)+find(rc,x,y);
//目标区间左右都有分布
}
//重点详解:
//void pushdown(int);//下放惰性标记
//此处标记指的是惰性标记,实际上是让子节点暂时处于
//不更新状态等到用到的时候在做更新而区间加时要把和
//那个区间有关的区间全部价值,不然不配合输出
//超时了就要优化。
//你想啊,虽然x~y区间要增加一个值,难道这个区间就一
//定要用吗?
//如果区间值增加了但后来没有询问,我们一开始为什么
//要增加呢?
//正如背古文,如果考试不考,我们为什么要背呢?
//所以lazy思想就怎么产生了。
//lazy,就是懒嘛,就是先不被古文,等到考试要考了再
//去背嘛;
//先不增加区间,等到询问时在去增加嘛;
//我们可以搞一个add数组,专门把编号为num的区间要加
//的值记录下来
//如果要用了,再给线段树num加上v[num]的值,再把
//add[num]
//传给v[num*2]和v[num*2+1];然后v[num]清零
//“如果要用了”这一步用一个clean函数实现
//void buildtree(int,int,int);//构造线段树
//void update(int,int,int,long long);//区间修改
//1,起始区间,终止区间,修改值
//long long find(int,int,int);//区间查询
//1,区间起始值,区间终止值
int main()
{
int n,m,flag;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
buildtree(1,1,n);//构造线段树
for(int i=0;i<m;i++){
scanf("%d",&flag);
if(flag==1){
scanf("%d%d%lld",&x,&y,&k);
update(1,x,y,k);
}
if(flag==2){
scanf("%d%d",&x,&y);
printf("%lld\n",find(1,x,y));
}
}
return 0;
}
//假设线段树处理的最大长度是n,那么总结点数不超过2*n
//线段树分解数量级:线段树能把任意一条长度为M的线段分为
//不超过2log(M)条线段,很大的数一下子变小,使得线段树查
//询与修改复杂度都在O(log2(n))范围内能得以解决
//线段树是一棵二叉树,深度约为log2n左右
//所以线段树空间消耗O(n),由于他的深度性质,解决问题效率
//更高