线段树常用技巧模版(刷题篇)

Posted KaaaterinaX

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线段树常用技巧模版(刷题篇)相关的知识,希望对你有一定的参考价值。

这篇博客相当于线段树学习博客的补充。
原文链接:2021-07-27 重见线段树

一、双tag线段树

这种时候,如果不做特殊处理,tag添加的顺序将影响最终结果,十分麻烦。

由于操作含有乘法和加法,并且先乘后加与先加后乘结果不同。所以标记下传的时候要这样处理:

void pushdown(int u){
    
    //先乘后加,并下传懒标记
    tr[u<<1].sum=(tr[u<<1].sum*tr[u].mu)%p;
    tr[u<<1].sum=(tr[u<<1].sum+(tr[u<<1].r-tr[u<<1].l+1)*tr[u].ad)%p;
    tr[u<<1].ad=(tr[u].ad+tr[u<<1].ad*tr[u].mu)%p;//子节点需要ad标记的更新与父节点的mu有关
    tr[u<<1].mu=(tr[u<<1].mu*tr[u].mu)%p;
    
    tr[u<<1|1].sum=(tr[u<<1|1].sum*tr[u].mu)%p;
    tr[u<<1|1].sum=(tr[u<<1|1].sum+(tr[u<<1|1].r-tr[u<<1|1].l+1)*tr[u].ad)%p;
    tr[u<<1|1].ad=(tr[u].ad+tr[u<<1|1].ad*tr[u].mu)%p;
    tr[u<<1|1].mu=(tr[u<<1|1].mu*tr[u].mu)%p;
    //父节点懒标记回归默认
    tr[u].ad=0;
    tr[u].mu=1;
}

void modify1(int u,ll l,ll r,ll k){
    //区间*k
    if(tr[u].l>=l&&tr[u].r<=r){
        tr[u].sum=(tr[u].sum*k)%p;
        tr[u].ad=(tr[u].ad*k)%p;
        tr[u].mu=(tr[u].mu*k)%p;
        return;
    }
    if(tr[u].r<l||tr[u].l>r){
        return;
    }
    pushdown(u);
    modify1(u<<1,l,r,k);
    modify1(u<<1|1,l,r,k);
    tr[u].sum=(tr[u<<1].sum+tr[u<<1|1].sum)%p;
}

啊记得开ll。。
这种大数据都必须开ll,不然wa到怀疑人生。

完整代码如下:

#include <iostream>
#include <iomanip>
#include <stdio.h>
#include <math.h>
#include <algorithm>
#include <queue>
#include <stack>
#include <set>
//#include <unordered_set>
#include <string.h>
#include <map>
//#include <unordered_map>
#include <stdlib.h>
#include <time.h>
#include <vector>
#include <deque>
#define YES "YES"
#define NO "NO"
#define INF 0x3f3f3f3f
#define LINF 0x3f3f3f3f3f3f3f3f
#define FAST ios::sync_with_stdio(false)
#define ll long long
#define pb push_back
#define endl '\\n'
#define eps const double eps=1e-6
using namespace std;
//---------------------------------------------------//
template<typename T>inline void Re(T &x){
    x=0;
    char c=getchar();
    ll f=1;
    if(c>'9'||c<'0'){
        if(c=='-'){
            f=-1;
        }
        c=getchar();
    }
    while(c<='9'&&c>='0'){
        x=(x<<1)+(x<<3)+c-'0';
        c=getchar();
    }
    x*=f;
}
template<typename T> inline void write(T x){

    if(x<0){
        putchar('-');
        x=-x;
    }
    if(x>9){
        write(x/10);
    }
    putchar(x%10+'0');
}
//_____________________________________________________//

const int maxn=1e5+7;

int n,m;
ll p;

ll a[maxn];

struct tr{
    ll l,r;
    ll sum;
    ll ad,mu;//懒标记
}tr[maxn<<2];

void build(int u,ll l,ll r){
    tr[u].l=l;
    tr[u].r=r;
    tr[u].mu=1;
    if(l==r){
        tr[u].sum=a[l];
        return;
    }
    ll mid=(l+r)>>1;
    build(u<<1,l,mid);
    build(u<<1|1,mid+1,r);
    tr[u].sum=(tr[u<<1].sum+tr[u<<1|1].sum)%p;
}

void pushdown(int u){
    
    //先乘后加,并下传懒标记
    tr[u<<1].sum=(tr[u<<1].sum*tr[u].mu)%p;
    tr[u<<1].sum=(tr[u<<1].sum+(tr[u<<1].r-tr[u<<1].l+1)*tr[u].ad)%p;
    tr[u<<1].ad=(tr[u].ad+tr[u<<1].ad*tr[u].mu)%p;
    tr[u<<1].mu=(tr[u<<1].mu*tr[u].mu)%p;
    
    tr[u<<1|1].sum=(tr[u<<1|1].sum*tr[u].mu)%p;
    tr[u<<1|1].sum=(tr[u<<1|1].sum+(tr[u<<1|1].r-tr[u<<1|1].l+1)*tr[u].ad)%p;
    tr[u<<1|1].ad=(tr[u].ad+tr[u<<1|1].ad*tr[u].mu)%p;
    tr[u<<1|1].mu=(tr[u<<1|1].mu*tr[u].mu)%p;
    //父节点懒标记回归默认
    tr[u].ad=0;
    tr[u].mu=1;
}


void modify1(int u,ll l,ll r,ll k){
    //区间*k
    if(tr[u].l>=l&&tr[u].r<=r){
        tr[u].sum=(tr[u].sum*k)%p;
        tr[u].ad=(tr[u].ad*k)%p;
        tr[u].mu=(tr[u].mu*k)%p;
        return;
    }
    if(tr[u].r<l||tr[u].l>r){
        return;
    }
    pushdown(u);
    modify1(u<<1,l,r,k);
    modify1(u<<1|1,l,r,k);
    tr[u].sum=(tr[u<<1].sum+tr[u<<1|1].sum)%p;
}

void modify2(int u,ll l,ll r,ll k){
    //区间+k
    if(tr[u].l>=l&&tr[u].r<=r){
        tr[u].sum=(tr[u].sum+(tr[u].r-tr[u].l+1)*k)%p;
        tr[u].ad=(tr[u].ad+k)%p;
        return;
    }
    if(tr[u].r<l||tr[u].l>r){
        return;
    }
    pushdown(u);
    modify2(u<<1,l,r,k);
    modify2(u<<1|1,l,r,k);
    tr[u].sum=(tr[u<<1].sum+tr[u<<1|1].sum)%p;
}

ll query(int u,ll l,ll r){
    ll sum=0;
    if(tr[u].l>=l&&tr[u].r<=r){
        sum+=tr[u].sum;
        return sum;
    }
    if(tr[u].r<l||tr[u].l>r){
        return 0;
    }
    pushdown(u);
    sum+=query(u<<1,l,r);
    sum+=query(u<<1|1,l,r);
    return sum;
}

int main(){
    cin>>n>>m>>p;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    build(1,1,n);
    while(m--){
        int x;
        cin>>x;
        if(x==1){
            //区间内乘k
            ll l,r,k;
            cin>>l>>r>>k;
            modify1(1,l,r,k);
        }
        if(x==2){
            ll l,r,k;
            cin>>l>>r>>k;
            modify2(1,l,r,k);
        }
        if(x==3){
            ll l,r;
            cin>>l>>r;
            cout<<query(1,l,r)%p<<endl;
        }
    }
}

以上是关于线段树常用技巧模版(刷题篇)的主要内容,如果未能解决你的问题,请参考以下文章

二叉树刷题篇(10) 二叉搜索树

二叉树刷题篇镜像二叉树与二叉树深度

二叉树刷题篇(完) 二叉搜索树的修剪构造与转换

二叉树刷题篇(11) 二叉搜索树的最小绝对差与众数

二叉树刷题篇(咕)

二叉树刷题篇平衡二叉树与二叉树的所有路径