【模板】线段树

线段树1链接:https://www.luogu.org/problemnew/show/P3372

线段树2链接:https://www.luogu.org/problemnew/show/P3373

线段树是一种维护区间的数据结构,且满足二叉树的全部性质

下图是一棵维护区间$[1,6]$的线段树↓

格式:$id_{l,r}$

我们可以发现,对于每个节点$k$来说,其左节点编号为$2k$,右节点编号为$2k+1$,左节点维护区间$[l,(l+r)/2]$,右节点维护区间$[(l+r)/2+1,r]$ 这样我们就得到了递归建树的伪代码:

build(k,l,r)
    if(l=r)
        获取节点信息
        return
    i←2k,mid←(l+r)/2
    build(i,l,mid)
    build(i+1,mid+1,r)
    合并子节点信息
end

然后我们考虑区间查询操作

比方说我要查询区间$[1,4]$ 的信息

我们只需要获取节点$2$与节点$12$ 的信息,就可以通过合并信息的方式获取到整块区间的信息

这一步也是可以递归求解的,只需要对维护区间判断即可,下面给出伪代码

ask(k,l,r,le,ri)
    if(le<=l and r<=ri) return 节点k信息
    i←2k,mid←(l+r)/2
    sum←NULL
    if(le<=mid) 合并sum,ask(i,l,mid,le,ri)
    if(mid<ri) 合并sum,ask(i+1,mid+1,r,le,ri)
    return sum信息
end

如果要进行区间修改呢?

我们仍以修改$[1,4]$ 为例

我们需要修改节点$2$ 与节点 $12$ 的信息,并将修改计入上层节点的贡献

对于多个标记贡献与顺序有关的情况,我们采用标记下传的方式解决,下面是伪代码

po(k,l,r)
    if(l=r or 当前节点没有标记) return
    标记下传
    清空当前层标记
end

cchg(k,l,r,le,ri)
    po(k,l,r)
    if(le<=l and r<=ri)
        修改当前节点
        return
    i←2k,mid←(l+r)/2
    cchg(i,l,mid,le,ri)
    cchg(i+1,mid+1,r,le,ri)
    合并下层节点信息
end

这样,我们就解决了序列区间修改与查询的问题

那么以线段树2为例,我们给出C++的代码↓

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

const int MAXN=1<<17;

int n,m,MOD,x,p,l,r;
int a[MAXN];
long long tree[MAXN<<1],add[MAXN<<1],mul[MAXN<<1];

void build(int k,int l,int r){
    mul[k]=1;
    if(l==r){
        tree[k]=a[l];
        return;
    }int i=k<<1,mid=l+r>>1;
    build(i,l,mid);
    build(i|1,mid+1,r);
    tree[k]=tree[i]+tree[i|1];
}

void po(int k,int l,int r){
    if(l==r||add[k]==0&&mul[k]==1) return;
    int i=k<<1,mid=l+r>>1;
    mul[i]=(mul[i]*mul[k])%MOD;
    mul[i|1]=(mul[i|1]*mul[k])%MOD;
    add[i]=(add[i]*mul[k]+add[k])%MOD;
    add[i|1]=(add[i|1]*mul[k]+add[k])%MOD;
    tree[i]=(tree[i]*mul[k]+add[k]*(mid-l+1))%MOD;
    tree[i|1]=(tree[i|1]*mul[k]+add[k]*(r-mid))%MOD;
    add[k]=0,mul[k]=1;
}

void cadd(int k,int l,int r,int le,int ri,int x){
    po(k,l,r);
    if(le<=l&&r<=ri){
        add[k]=x;
        tree[k]=(tree[k]+x*(r-l+1))%MOD;
        return;
    }int i=k<<1,mid=l+r>>1;
    if(le<=mid) cadd(i,l,mid,le,ri,x);
    if(mid<ri) cadd(i|1,mid+1,r,le,ri,x);
    tree[k]=(tree[i]+tree[i|1])%MOD;
}

void cmul(int k,int l,int r,int le,int ri,int x){
    po(k,l,r);
    if(le<=l&&r<=ri){
        mul[k]=x;
        tree[k]=(tree[k]*x)%MOD;
        return;
    }int i=k<<1,mid=l+r>>1;
    if(le<=mid) cmul(i,l,mid,le,ri,x);
    if(mid<ri) cmul(i|1,mid+1,r,le,ri,x);
    tree[k]=(tree[i]+tree[i|1])%MOD;
}

long long ask(int k,int l,int r,int le,int ri){
    po(k,l,r);
    if(le<=l&&r<=ri) return tree[k]%MOD;
    int i=k<<1,mid=l+r>>1;
    long long sum=0;
    if(le<=mid) sum+=ask(i,l,mid,le,ri);
    if(mid<ri) sum+=ask(i|1,mid+1,r,le,ri);
    return sum%MOD;
}

void init(){
    scanf("%d%d%d",&n,&m,&MOD);
    for(int i=1;i<=n;++i) scanf("%d",&a[i]);
    build(1,1,n);
    return;
}

void solve(){
    while(m--){
        scanf("%d",&p);
        if(p==1) scanf("%d%d%d",&l,&r,&x),cmul(1,1,n,l,r,x);
        else if(p==2) scanf("%d%d%d",&l,&r,&x),cadd(1,1,n,l,r,x);
        else scanf("%d%d",&l,&r),printf("%lld\n",ask(1,1,n,l,r));
    }return;
}

int main(){
    init();
    solve();
    return 0;
}