线段树——懒标记下放

【模板】线段树 1

题目描述

如题,已知一个数列,你需要进行下面两种操作:

  1. 将某区间每一个数加上 k k k
  2. 求出某区间每一个数的和。

输入格式

第一行包含两个整数 n , m n, m n,m,分别表示该数列数字的个数和操作的总个数。

第二行包含 n n n 个用空格分隔的整数,其中第 i i i 个数字表示数列第 i i i 项的初始值。

接下来 m m m 行每行包含 3 3 3 4 4 4 个整数,表示一个操作,具体如下:

  1. 1 x y k:将区间 [ x , y ] [x, y] [x,y] 内每个数加上 k k k
  2. 2 x y:输出区间 [ x , y ] [x, y] [x,y] 内每个数的和。

输出格式

输出包含若干行整数,即为所有操作 2 的结果。

样例 #1

样例输入 #1

5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4

样例输出 #1

11
8
20

提示

对于 30 % 30\% 30% 的数据: n ≤ 8 n \le 8 n8 m ≤ 10 m \le 10 m10
对于 70 % 70\% 70% 的数据: n ≤ 10 3 n \le {10}^3 n103 m ≤ 10 4 m \le {10}^4 m104
对于 100 % 100\% 100% 的数据: 1 ≤ n , m ≤ 10 5 1 \le n, m \le {10}^5 1n,m105

保证任意时刻数列中所有元素的绝对值之和 ≤ 10 18 \le {10}^{18} 1018

【样例解释】

懒标记用于计算每个线段的变化程度,在这题就是每个线段增加的数之和,当我们的add操作和query操作都没有遍历到所输入的x y的时候,我们就需要不断的把懒标记进行下放,下放完之后我们还得记得维护f的值,使其不失真,具体看代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll f[400005],v[400005];
int n,m,a[100005];
inline void buildtree(int k,int l,int r){
    if(l==r){f[k]=a[l];return;}
    int mid=(l+r)>>1;
    buildtree(k<<1,l,mid);buildtree(k<<1|1,mid+1,r);
    f[k]=f[k<<1]+f[k<<1|1];
}
inline void push_down(int k){//将懒标记点下放
    v[k<<1]+=v[k],v[k<<1|1]+=v[k],v[k]=0;
}
inline void push_up(int k,int l,int r,int m){//维护f[k]的值,使其不失真
    f[k]=f[k<<1]+v[k<<1]*(m-l+1)+f[k<<1|1]+v[k<<1|1]*(r-m);
}
inline void add(int k,int l,int r,int x,int y,int num){
    if(l==x&&r==y){
        v[k]+=num;
        return;
    }
    if(v[k])push_down(k);
    int mid=(l+r)>>1;
    if(y<=mid)add(k<<1,l,mid,x,y,num);
    else
        if(x>mid)add(k<<1|1,mid+1,r,x,y,num);
        else add(k<<1,l,mid,x,mid,num),add(k<<1|1,mid+1,r,mid+1,y,num);
    push_up(k,l,r,mid);
}
inline ll query(int k,int l,int r,int x,int y){
    if(l==x&&r==y)return f[k]+v[k]*(r-l+1);
    if(v[k])push_down(k);
    int mid=(l+r)>>1;
    ll res;
    if(y<=mid)res=query(k<<1,l,mid,x,y);
    else
        if(x>mid)res=query(k<<1|1,mid+1,r,x,y);
        else res=query(k<<1,l,mid,x,mid)+query(k<<1|1,mid+1,r,mid+1,y);
    push_up(k,l,r,mid);
    return res;
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;++i)scanf("%d",&a[i]);
    buildtree(1,1,n);
    while(m--){
        int t,x,y,num;scanf("%d%d%d",&t,&x,&y);
        if(t==1){scanf("%d",&num);add(1,1,n,x,y,num);}
        else cout<<query(1,1,n,x,y)<<"\n";
    }
}

【模板】线段树 2

题目描述

如题,已知一个数列,你需要进行下面三种操作:

  • 将某区间每一个数乘上 x x x

  • 将某区间每一个数加上 x x x

  • 求出某区间每一个数的和

输入格式

第一行包含三个整数 n , m , p n,m,p n,m,p,分别表示该数列数字的个数、操作的总个数和模数。

第二行包含 n n n 个用空格分隔的整数,其中第 i i i 个数字表示数列第 i i i 项的初始值。

接下来 m m m 行每行包含若干个整数,表示一个操作,具体如下:

操作 1 1 1: 格式:1 x y k 含义:将区间 [ x , y ] [x,y] [x,y] 内每个数乘上 k k k

操作 2 2 2: 格式:2 x y k 含义:将区间 [ x , y ] [x,y] [x,y] 内每个数加上 k k k

操作 3 3 3: 格式:3 x y 含义:输出区间 [ x , y ] [x,y] [x,y] 内每个数的和对 p p p 取模所得的结果

输出格式

输出包含若干行整数,即为所有操作 3 3 3 的结果。

样例 #1

样例输入 #1

5 5 38
1 5 4 2 3
2 1 4 1
3 2 5
1 2 4 2
2 3 5 5
3 1 4

样例输出 #1

17
2

提示

【数据范围】

对于 30 % 30\% 30% 的数据: n ≤ 8 n \le 8 n8 m ≤ 10 m \le 10 m10
对于 70 % 70\% 70% 的数据:$n \le 10^3 , , m \le 10^4$
对于 100 % 100\% 100% 的数据:$ n \le 10^5 , , m \le 10^5$

除样例外, p = 571373 p = 571373 p=571373

(数据已经过加强_

样例说明:

故输出应为 17 17 17 2 2 2 40   m o d   38 = 2 40 \bmod 38 = 2 40mod38=2

对于这道题,我们首先能确认懒标记应该要有两个,因为对于add而言,懒标记的初始值应该为0,而对于mul而言,懒标记的初始值应该为1,两者肯定不可能是同一个懒标记。
但是对于懒标记mul,初始化起来比较复杂,因此最好的方式就是就是在建树的时候初始化为1,这时候构造结构体的必要性就有了。
下放需要有个规则,首先,下放的时候对于add而言,应该乘以父亲结点的mul,再加上父亲结点的add;对于mul而言,直接乘以父亲结点的mul即可;对于val而言,其等于自己原本的val父亲的mul再加上线段长度父亲的add,说起来有点绕,具体看代码即可

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+5;
int n,m,p,a[N];
struct node{
    ll val,add,mul;
    int len;
}t[4*N];
inline void buildtree(int k,int l,int r){
    t[k].add=0,t[k].mul=1;
    t[k].len=(r-l+1);
    if(l==r){t[k].val=a[l];return;}
    int mid=(l+r)>>1;
    buildtree(k<<1,l,mid),buildtree(k<<1|1,mid+1,r);
    t[k].val=(t[k<<1].val+t[k<<1|1].val)%p;
}
inline void push_down(int k){
    t[k<<1].add=(t[k<<1].add*t[k].mul+t[k].add)%p;
    t[k<<1].mul=(t[k<<1].mul*t[k].mul)%p;
    t[k<<1].val=t[k<<1].val*t[k].mul%p+t[k<<1].len*t[k].add%p;
    t[k<<1|1].add=(t[k<<1|1].add*t[k].mul+t[k].add)%p;
    t[k<<1|1].mul=t[k<<1|1].mul*t[k].mul%p;
    t[k<<1|1].val=t[k<<1|1].val*t[k].mul%p+t[k<<1|1].len*t[k].add%p;
    t[k].mul=1,t[k].add=0;
}
inline void push_up(int k){
    t[k].val=(t[k<<1].val+t[k<<1|1].val)%p;
}
inline void modify(int k,int l,int r,int x,int y,int add,int mul){
    if(l==x&&y==r){
        t[k].val=(t[k].val*mul%p+t[k].len*add)%p;
        t[k].mul=t[k].mul*mul%p;
        t[k].add=(t[k].add*mul%p+add)%p;
        return;
    }
    if(t[k].add!=0||t[k].mul!=1)push_down(k);
    int mid=(l+r)>>1;
    if(y<=mid)modify(k<<1,l,mid,x,y,add,mul);
    else
        if(x>mid)modify(k<<1|1,mid+1,r,x,y,add,mul);
        else modify(k<<1,l,mid,x,mid,add,mul),modify(k<<1|1,mid+1,r,mid+1,y,add,mul);
    push_up(k);
}
inline ll query(int k,int l,int r,int x,int y){
    if(l==x&&r==y)return t[k].val;
    if(t[k].add!=0||t[k].mul!=1)push_down(k);
    int mid=(l+r)>>1;
    ll res;
    if(y<=mid)res=query(k<<1,l,mid,x,y);
    else
        if(x>mid)res=query(k<<1|1,mid+1,r,x,y);
        else res=(query(k<<1,l,mid,x,mid)+query(k<<1|1,mid+1,r,mid+1,y))%p;
    return res;
}
int main(){
    cin>>n>>m>>p;
    for(int i=1;i<=n;++i)scanf("%d",&a[i]);
    buildtree(1,1,n);
    while(m--){
        int c,x,y,k;scanf("%d%d%d",&c,&x,&y);
        if(c==1){
            scanf("%d",&k);
            modify(1,1,n,x,y,0,k);
        }
        else if(c==2){
            scanf("%d",&k);
            modify(1,1,n,x,y,k,1);
        }
        else printf("%lld\n",query(1,1,n,x,y));
    }
}

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

布布要成为最负责的男人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值