线段树模板 (非zkw线段树)

  • 引入

如果我们给定了一个序列,我们经常要对这个序列进行区间操作。

  1. 对于区间[l,r],求和(或者求最值)。
  2. 对区间[l,r]的每个数进行四则运算。

一种可行的方法是前缀和,用一个数组presum求出i前面所有数字的和。那么操作1的实现就很简单了直接输出presum[r]-presum[l-1], O(1)的复杂度,很给力。但是操作2的话就不得劲了,我们要对[l,r]每一个单元进行更新,最坏情况是O(N)。那么解决这类问题的正确操作有哪些呢?接下来我就介绍其中一种——线段树。

  • 线段树简介

线段树经常被用来解决区间问题,通过分块的思想,将各个区间内的值进行储存,因为二叉树的形式,修改和查询都能达到O\left ( logN \right )的复杂度。但是代价就是空间。一般的话都要开四倍的空间来存线段树。(具体原因百度!)

看,这就是一颗有灵魂的线段树了,绿色的代表它在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线段树啦!

猜你喜欢

转载自blog.csdn.net/ffscas/article/details/86659005