线段树-进阶

上一次我们写的线段树已经可以解决区间查询、单点修改了!可喜可贺

那如果现在我们需要区间修改、区间查询呢?

一般有两种思路:lazytag和标记永久化,lazytag的使用面好像更广一些。

一、lazytag

比如我们现在要修改一个区间,我们可以像查询一样分成若干段,然后分别修改每一段。

那么问题来了,每一段要怎么修改?如果直接修改sum,询问就乱套了。如果暴力修改,最坏复杂度O(n),那还要线段树干嘛(╯‵□′)╯掀桌

那我们这么想,我们修改就不修改整个区间了,而是在区间上维护一个标记。

那我们查询的时候要怎么办呢?我们一路走一路下传标记,就是说把这个点的标记清掉,更新本节点的信息,并把标记传给儿子。

那这样又有一个问题,如果我们修改了一个点的孩子,然后查询的时候只查询到上面的这个点,那信息不就更新不到了吗?

那我们查询的时候还要用儿子节点的信息顺便更新一下本节点的信息。

说了这么多…写起来好像还是蛮简单的。

下面这份代码的码风比较奇怪…就是只建了满二叉树,没有按n来建…代码里的ls和rs表示区间的左右端点…

//codevs1082 区间加一个数 区间查询和
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#include <string.h>
#include <math.h>
#include <set>
#include <map>
using namespace std;
int n;
typedef long long ll;
#define SZ 555555
int M=262144,M2=M+M,ls[SZ],rs[SZ];
ll sum[SZ],tag[SZ];
void build()
{
    for(int i=M+1;i<=M+M;i++) ls[i]=rs[i]=i-M;
    for(int i=M-1;i;i--) ls[i]=ls[i+i], rs[i]=rs[i+i+1], sum[i]=sum[i+i]+sum[i+i+1];
}
void pd(int x)
{
    if(tag[x])
    {
        sum[x]+=tag[x]*(rs[x]-ls[x]+1);
        if(x+x<=M2) tag[x+x]+=tag[x], tag[x+x+1]+=tag[x];
        tag[x]=0;
    }
}
void upd(int x)
{
    pd(x+x); pd(x+x+1);
    sum[x]=sum[x+x]+sum[x+x+1];
}
void edit(int x,int ql,int qr,int v)
{
    if(x>M2||ql>qr) return;
    pd(x);
    if(ql==ls[x]&&qr==rs[x]) {tag[x]+=v; return;}
    int mid=ls[x]+rs[x]>>1;
    edit(x+x,ql,min(qr,mid),v);
    edit(x+x+1,max(mid+1,ql),qr,v);
    upd(x);
}
ll gsum(int x,int ql,int qr)
{
    if(x>M2||ql>qr) return 0;
    pd(x);
    if(ql==ls[x]&&qr==rs[x]) return sum[x];
    int mid=ls[x]+rs[x]>>1;
    ll ans=gsum(x+x,ql,min(qr,mid))+gsum(x+x+1,max(mid+1,ql),qr);
    upd(x); return ans;
}
int q,a,b,c;
char buf[3];
void readdata()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%lld",&sum[i+M]);
    scanf("%d",&q); build();
    while(q--)
    {
        scanf("%s",buf);
        if(buf[0]=='2')
        {
            scanf("%d%d",&a,&b);
            printf("%lld\n",gsum(1,a,b));
        }
        else
        {
            scanf("%d%d%d",&a,&b,&c);
            edit(1,a,b,c);
        }
    }
}
int main()
{
    readdata();
}

二、标记永久化

你可能会觉得:lazytag使用起来很方便,感觉也很强大,为啥还要什么标记永久化?

等你学了主席树你就明白了 反正多学总没有什么问题233

标记永久化就是如果我们要修改一个区间,还是在区间上打一个标记,但是不下传!

——啥,不下传如何保证正确性?

我们在修改的路径上更新这个区间的和,然后在返回和的时候把标记累加进去,这样就可以保证正确性了。

//codevs1082 区间加一个数 区间查询和
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#include <string.h>
#include <math.h>
#include <set>
#include <map>
using namespace std;
int n;
typedef long long ll;
#define SZ 555555
int MAXN=524288;
ll sum[SZ],tag[SZ];
void edit(int x,int ql,int qr,int v,int l,int r)
{
    if(x>MAXN||ql>qr||l>r) return;
    if(ql==l&&qr==r) {tag[x]+=v; return;}
    sum[x]+=(qr-ql+1)*v;
    int mid=l+r>>1;
    edit(x+x,ql,min(qr,mid),v,l,mid);
    edit(x+x+1,max(mid+1,ql),qr,v,mid+1,r);
}
ll gsum(int x,int ql,int qr,int l,int r)
{
    if(x>MAXN||ql>qr) return 0;
    if(ql==l&&qr==r) return sum[x]+tag[x]*(qr-ql+1);
    int mid=l+r>>1;
    return gsum(x+x,ql,min(qr,mid),l,mid)+gsum(x+x+1,max(mid+1,ql),qr,mid+1,r)+tag[x]*(qr-ql+1);
}
int q,a,b,c;
char buf[3];
void readdata()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int a=i,b; scanf("%d",&b);
        edit(1,a,a,b,1,n);
    }
    scanf("%d",&q);
    while(q--)
    {
        scanf("%s",buf);
        if(buf[0]=='2')
        {
            scanf("%d%d",&a,&b);
            printf("%lld\n",gsum(1,a,b,1,n));
        }
        else
        {
            scanf("%d%d%d",&a,&b,&c);
            edit(1,a,b,c,1,n);
        }
    }
}
int main()
{
    readdata();
}

我相信你学完这两种方法,线段树的题都可以随手秒啦!

线段树-进阶_第1张图片

你可能感兴趣的:(线段树-进阶)