线段树
线段树VS树状数组
·线段树功能更多
·树状数组写起来方便
线段树的构成
线段树中每一个节点都是其父节点的左一半或右一半,如果是奇数除不尽的话,中间的那个数归于左一半。并且其中父节点的子子节点编号永远都是父节点的编号的两倍或两倍多1.
区间修改,区间查询
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <algorithm>
#include <cmath>
long long n,m;
long long tree[2000020],lazy[2000020];//lazy标记用于以后的整个区间都加一的时候
using namespace std;
void down(long long p,long long l, long long r){
long long mid=(l+r)/2;
if(lazy[p]==0){//如果没有标上lazy标记,直接通过
return ;
}else{//将其子节点标上,并把其子节点增加,把自己的lazy消掉
tree[p*2]+=(mid-l+1)*lazy[p];
tree[p*2+1]+=(r-mid)*lazy[p];
lazy[p*2]+=lazy[p];
lazy[p*2+1]+=lazy[p];
lazy[p]=0;
}
}
//把p节点区间[l,r]中的[x,y]增加z
void modify(long long p,long long l,long long r,long long x,long long y,long long z){
//如果整个区间都是完全属于查询区域的话做懒惰标记(lazy[])
if(x<=l&&r<=y){
tree[p]+=(r-l+1)*z;//将自己先加上所有叶子节点所加的数
lazy[p]+=z;
return ;
}else{
//计算区间划分
down(p,l,r);//先将刚才的父节点的子节点全都加上一定的数,并且把lazy标记消掉,然后把子节点表上lazy标记,因为后面还有需要加的
long long mid=(l+r)/2;
//左
if(x<=mid){
modify(p*2,l,mid,x,y,z);
}
//右
if(y>=mid+1){
modify(p*2+1,mid+1,r,x,y,z);
}
//修改好子树之后更新当前节点
tree[p]=tree[p*2]+tree[p*2+1];
}
}
//在p节点的区间[l,r]种,查询[x,y]的区间和
long long query(long long p,long long l,long long r,long long x,long long y){
//当前区域完全属于所要查询的区域,直接返回
if(x<=l&&r<=y){
return tree[p];
}
//左右区间划分
down(p,l,r);
long long res=0;
long long mid=(l+r)/2;
//左
if(x<=mid){
res+=query(p*2,l,mid,x,y);//如果左边有,就继续查找其左节点
}
//右
if(y>=mid+1){
res+=query(p*2+1,mid+1,r,x,y);
}
return res;
}
int main() {
cin>>n>>m;
memset(tree,0,sizeof(tree));
memset(lazy,0,sizeof(lazy));
for(long long i=1;i<=n;i++){
long long tmp;
cin>>tmp;
modify(1,1,n,i,i,tmp);//初始化整棵树,第一个1是要从跟节点往下,1代表跟节点编号,第二个1以及第三个n是表示节点所包括的范围,i,i,tmp即为需要增加的区间(也可以说是节点)以及增加的数量tmp
}
for(long long i=1;i<=m;i++){
long long type,x,y;
cin>>type>>x>>y;
if(type==1){//区间修改
long long z;
cin>>z;
modify(1,1,n,x,y,z);//第一个1是要从根节点往下,1代表根节点。第二个1和第三个n是表示节点所包括的范围,x,y,z即为需要增加的区间以及增加的数目。
}else{
cout<<query(1,1,n,x,y)<<endl;//第一个1是要从根节点往下,1代表根节点。第二个1和第三个n是表示节点所包括的范围,x,y即为需要查询的范围。
}
}
return 0;
}