洛谷P3372 【模板】线段树1
题目描述
如题,已知一个数列,你需要进行下面两种操作:
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
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=100005;
int n,m;
int arr[maxn];
struct tree
{
long long pre,add;
//pre记录线段树区间和,add为懒标记
}t[4*maxn];//线段树不要忘记开四倍空间!
void scanff()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&arr[i]);
}
//建树操作,将初始数组递归放入线段树中。
//实际应用中有不少情况并不需要建树(一个个插入)
void build(int p,int l,int r)
{
if(l==r)
{
t[p].pre=arr[l];
return;
}
int mid=(l+r)>>1;
build(2*p,l,mid);
build(2*p+1,mid+1,r);
t[p].pre=t[2*p].pre+t[2*p+1].pre;
}
//下传懒标记,并清空当前对懒标记
void spread(int p,int l,int r)
{
int mid=(l+r)>>1;
if(t[p].add)
{
t[2*p].pre+=(mid-l+1)*t[p].add;
t[2*p+1].pre+=(r-mid)*t[p].add;
t[2*p].add+=t[p].add;
t[2*p+1].add+=t[p].add;
t[p].add=0;
}
}
void change(int p,int l,int r,int x,int y,int z)
{
if(x>r||y<l) return;
if(x<=l&&y>=r)
{
//懒标记是对儿子节点们用的。赋予懒标记之前,当前区间和已经更新成功
t[p].pre+=(r-l+1)*z;
t[p].add+=z;
return;
}
//需要用到儿子节点时,才下传懒标记
spread(p,l,r);
int mid=(l+r)>>1;
change(p*2,l,mid,x,y,z);
change(p*2+1,mid+1,r,x,y,z);
//更新父亲节点对值:儿子节点之和
t[p].pre=t[p*2].pre+t[p*2+1].pre;
}
long long ask(int p,int l,int r,int x,int y)
{
if(y<l||x>r) return 0;
if(x<=l&&y>=r) return t[p].pre;
spread(p,l,r);
long long ans=0;
int mid=(r+l)>>1;
ans+=ask(2*p,l,mid,x,y);
ans+=ask(2*p+1,mid+1,r,x,y);
return ans;
}
void work()
{
build(1,1,n);
for(int i=1;i<=m;i++)
{
int p,x,y,z;
scanf("%d",&p);
if(p==1)
{
scanf("%d%d%d",&x,&y,&z);
change(1,1,n,x,y,z);
}
if(p==2)
{
scanf("%d%d",&x,&y);
printf("%lld\n",ask(1,1,n,x,y));
}
}
}
int main()
{
// freopen("in.in","r",stdin);
// freopen("out.out","w",stdout);
scanff();
work();
return 0;
}