-
引入
如果我们给定了一个序列,我们经常要对这个序列进行区间操作。
- 对于区间[l,r],求和(或者求最值)。
- 对区间[l,r]的每个数进行四则运算。
一种可行的方法是前缀和,用一个数组presum求出i前面所有数字的和。那么操作1的实现就很简单了直接输出presum[r]-presum[l-1], 的复杂度,很给力。但是操作2的话就不得劲了,我们要对[l,r]每一个单元进行更新,最坏情况是。那么解决这类问题的正确操作有哪些呢?接下来我就介绍其中一种——线段树。
-
线段树简介
线段树经常被用来解决区间问题,通过分块的思想,将各个区间内的值进行储存,因为二叉树的形式,修改和查询都能达到的复杂度。但是代价就是空间。一般的话都要开四倍的空间来存线段树。(具体原因百度!)
看,这就是一颗有灵魂的线段树了,绿色的代表它在tree数组里的下标,黑色下标的是叶子结点以及区间。
其中,tree数组种下标为1的是根结点,他储存的是区间[1,5]内元素的和,它的左结点下标是1*2,存的是区间[1,3]元素的和,右节点下标是1*2+1,存的是区间[4,5]的和。
-
建树
通过递归向下建树,递归基就是到达叶子节点,然后在回溯的过程中,给父节点赋值。那么我们这个建树函数需要三个参数,l,r,p。l,r是用来表示当前的建树区间的。如果l==r了,那么就是说明到达叶子节点了。p就是代表当前在线段树数组的下标。(我们用数组来存这颗树。)那么如果父节点下标是p,左结点是p<<1(p*2),右节点是p<<1|1(p*2+1)。至于为什么用位运算,装逼啊快啊。然后就是回溯的函数了,tree[p]=tree[lch(p)]+tree[rch(p)];
#define inline il
typedef long long ll
const int maxn=1e6+5;
int tree[max<<2];
int pre[maxn];
il int lch(int p){return p<<1;}
il int rch(int p){retutn p<<1|1;}
il void pushup(int p){tree[p]=tree[lch(p)]+tree[rch(p)];}
void build(int l,int r,int p){
if(l==r){//如果当前区间左右相等了,必然是叶子节点了,那么给tree[p]赋值
tree[p]=pre[l];
return;
}
int mid=(l+r)>>1;//中间
build(l,mid,lch(p));
build(mid+1,r,rch(p));
pushup(p);//向上回溯,把整全部区间补满
}
-
修改
修改函数有六个参数,u_l,u_r,l,r,p,k,分别代表:要修改的区间,现在的区间,现在所在的下标,要修改的数(加减乘除)。那么怎么判断要不要往下递归查询了呢? 1.如果当前区间包含在要查询的区间里了, 那么不用往下了 2.如果当前区间和要查询的区间有交集,那么我们继续往下,把属于查询区间的部分返回回来。所以updata很简单了。那么我们就弄点花头精优化一下吧。我们弄个数组叫做懒人标记,我们给这个地方打个标记,意思就是我把这个p更新过了,但是他的子节点我还没去更新。那么等下如果在查询或者修改到他的子节点的时候,我们就可以通过这个懒人标记修改子节点了。详情见代码
int lazytag[maxn<<2];
il void cg(int l,int r,int p,int k){
tree[p]+=(r-l+1)*k;
lazytag[p]+=k;
}//改变tree[p]的值,增加p的懒人标记。
il void pushtagdown(int l,int r,int p,){
int mid=(l+r)>>1;
cg(l,mid,lch(p),lazytag[p]);
cg(mid+1,r,rch(p),lazytag[p]);
lazytag[p]=0;
}//往下传递懒人标签,先修改cg左右结点,然后把自己的标签清空(传下去了,那么他就不用管了)
void update(int l,int r,int u_l,int u_r,int p,int k){
if(l>=u_l&&r<=u_r){//如果这个区间在修改区间里
cg(p);//修改
return ;//回快乐老家
}
pushtagdown(l,r,p);//不然的话我就先传递标签下去,这样下面的标签就能被更新到了
int mid=(l+r)>>1;
if(u_l<=mid) update(l,mid,u_l,u_r,lch(p),k);//如果有交集,更新
if(u_r>mid) udata(mid+1,r,u_l,u_r,rch(p),k);
pushup(p);//回溯修改,因为你可能只修改了它一边的区间,所以要pushup更新一下。
}
- 查询
查询的话和update如出一辙,要注意的是由于lazytag的存在,所以要先pushdown标签,确保子节点被更新以后再query子节点。所以很显然啦!
ll query(ll l,ll r,ll ql,ll qr,ll p){
if(l>=ql&&r<=qr)
return tree[p];
pushdown(l,r,p);
ll ans=0;
ll mid=(l+r)>>1;
if(ql<=mid) ans+=query(l,mid,ql,qr,lch(p));
if(qr>mid) ans+=query(mid+1,r,ql,qr,rch(p));
return ans;
}//如果update懂了那么query应该没什么可以解释的了吧!
那么就完结撒花了那么我们看一下全部代码吧!
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<map>
#include<stack>
#include<string>
#include<algorithm>
#include<cmath>
#define rg register
#define il inline
using namespace std;
typedef unsigned long long ll;
const ll maxn=1000005;
ll tree[maxn<<2];
ll lazytag[maxn<<2];
ll pre[maxn];
il ll lch(ll p){return p<<1;}
il ll rch(ll p){return p<<1|1;}
il void pushup(ll p){tree[p]=tree[lch(p)]+tree[rch(p)];}
void build(ll l,ll r,ll p){
if(l==r){tree[p]=pre[l];return;}
ll mid = (l+r)>>1;
build(l,mid,lch(p));
build(mid+1,r,rch(p));
pushup(p);
}
il void cg(ll l,ll r,ll p,ll k){lazytag[p]+=k;tree[p]+=(r-l+1)*k;}
il void pushdown(ll l,ll r,ll p){
ll mid=(l+r)>>1;
cg(l,mid,lch(p),lazytag[p]);
cg(mid+1,r,rch(p),lazytag[p]);
lazytag[p]=0;
}
void update(ll l,ll r,ll ul,ll ur,ll p,ll k){
if(l>=ul&&r<=ur){
cg(l,r,p,k);
return;
}
pushdown(l,r,p);
ll mid=(l+r)>>1;
if(ul<=mid) update(l,mid,ul,ur,lch(p),k);
if(ur>mid) update(mid+1,r,ul,ur,rch(p),k);
//pushup(p);
}
ll query(ll l,ll r,ll ql,ll qr,ll p){
if(l>=ql&&r<=qr)
return tree[p];
pushdown(l,r,p);
ll ans=0;
ll mid=(l+r)>>1;
if(ql<=mid) ans+=query(l,mid,ql,qr,lch(p));
if(qr>mid) ans+=query(mid+1,r,ql,qr,rch(p));
return ans;
}
il ll read(){
ll ans=0;
char ch;
ll flag=1;
while((ch=getchar())==' '||ch=='\r'||ch=='\n');
if(ch==-1)return ans;if(ch=='-') flag=-1; else ans=ch^=48;
while((ch=getchar())>='0'&&ch<='9') ans=(ans<<3)+(ans<<1)+(ch^48);
return ans*flag;
}
int tot,qs;
void init(){
tot=read();
qs=read();
for(rg int i=1;i<=tot;i++)
pre[i]=read();
build(1,tot,1);
}
int main(){
init();
ll o,l,r,k;
for(rg int i=0;i<qs;i++){
o=read();
if(o==1){
l=read();r=read();k=read();
update(1,tot,l,r,1,k);
}
else{
l=read();r=read();
printf("%lld\n",query(1,tot,l,r,1));
}
}
return 0;
}
接下来就可以学zkw线段树啦!