【浮*光】 #线段树# bzoj 1798 维护序列

【bzoj 1798】【luogu p3373】线段树模板题

  • 序列a1,a2,…,aN。有如下三种操作形式:
  • (1)把数列中的一段数全部乘一个值;
  • (2)把数列中的一段数全部加一个值;
  • (3)询问数列中的一段数的和。
  • 由于答案可能很大,你只需输出这个数模P的值。

【分析】线段树,维护两个标记,加为 tmp_1,乘为 tmp_2。

加x的操作变为:乘1+x ; 乘c的操作变为:乘c+0 。

设当前区间长度为 len,当前区间需要乘 c 加 x,

当前区间原有标记为 c′ 和 x′,当前区间和为 sum 。

很明显:sum=c∗sum+x∗len

如何更新 c′ 和 x′ 呢? 设原区间和为 sum′ 。

sum=c∗sum+x∗len=c∗(c′∗sum′+x′∗len)+x∗len

所以 sum=c∗c′∗sum′+(c∗add′+add)∗len

这样就得出:c′=c∗c′ , x′=c∗x′+x 。

下传标记时,先下传乘法标记,再下传加法标记 

#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef long long LL;
typedef unsigned long long ull;

/*【bzoj 1798】序列a1,a2,…,aN。有如下三种操作形式: 
(1)把数列中的一段数全部乘一个值; (2)把数列中的一段数全部加一个值; 
(3)询问数列中的一段数的和。由于答案可能很大,你只需输出这个数模P的值。*/

#define MAXN 100005

struct tree_type{
    int l,r;
    LL tmp_1,tmp_2,sum;
}t[MAXN*4];
int n,m,p,opt,L,R,c,a[MAXN];

void build(int num,int l,int r){ //建树
    t[num].l=l; t[num].r=r;
    t[num].tmp_1=0; t[num].tmp_2=1;
    if(l==r){ t[num].sum=a[l]%p; return; }
    int mid=(l+r)/2; build(num*2,l,mid); build(num*2+1,mid+1,r);
    t[num].sum=(t[num*2].sum+t[num*2+1].sum)%p;
    return;
}

void push_down(int num){ //标记下移
    if(t[num].tmp_2==1&&t[num].tmp_1==0) return; //+0并且*1,不影响
    
    t[num*2].tmp_1=(t[num*2].tmp_1*t[num].tmp_2+t[num].tmp_1)%p; //加法标记
    t[num*2+1].tmp_1=(t[num*2+1].tmp_1*t[num].tmp_2+t[num].tmp_1)%p;
    
    t[num*2].tmp_2=(t[num*2].tmp_2*t[num].tmp_2)%p; //乘法标记
    t[num*2+1].tmp_2=(t[num*2+1].tmp_2*t[num].tmp_2)%p;
    
    t[num*2].sum=(t[num*2].sum*t[num].tmp_2+
        (t[num*2].r-t[num*2].l+1)*t[num].tmp_1)%p;
    t[num*2+1].sum=(t[num*2+1].sum*t[num].tmp_2+
        (t[num*2+1].r-t[num*2+1].l+1)*t[num].tmp_1)%p; //标记记入总和中
    
    t[num].tmp_1=0; t[num].tmp_2=1; return; //记得归零
}

void add(int num,int l,int r,int x){ //加法
    if(t[num].l>r||t[num].r<l) return;
    if(t[num].l>=l&&t[num].r<=r){
        t[num].tmp_1=(t[num].tmp_1+x)%p; //标记
        t[num].sum=(t[num].sum+(t[num].r-t[num].l+1)*x)%p;
        return;
    }
    push_down(num); //标记下移
    add(num*2,l,r,x); add(num*2+1,l,r,x);
    t[num].sum=(t[num*2].sum+t[num*2+1].sum)%p;
    return;
}

void multiply(int num,int l,int r,int x){ //乘法
    if(t[num].l>r||t[num].r<l) return;
    if(t[num].l>=l&&t[num].r<=r){
        t[num].tmp_1=(t[num].tmp_1*x)%p;
        t[num].tmp_2=(t[num].tmp_2*x)%p;
        t[num].sum=(t[num].sum*x)%p;
        return;
    }
    push_down(num); //标记下移
    multiply(num*2,l,r,x); multiply(num*2+1,l,r,x);
    t[num].sum=(t[num*2].sum+t[num*2+1].sum)%p;
    return;
}

LL ask(int num,int l,int r){ //区间查询
    if(t[num].l>r||t[num].r<l) return 0; //无重叠
    if(t[num].l>=l&&t[num].r<=r) return t[num].sum%p; //全包含
    push_down(num); //标记下移
    return (ask(num*2,l,r)+ask(num*2+1,l,r))%p; //递归左右子树
}

int main(){
    scanf("%d%d%d",&n,&m,&p);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    build(1,1,n); 
    for(int i=1;i<=m;i++){
        scanf("%d",&opt);
        if(opt==1){
            scanf("%d%d%d",&L,&R,&c);
            multiply(1,L,R,c); //乘法
        }
        if(opt==2){
            scanf("%d%d%d",&L,&R,&c);
            add(1,L,R,c); //加法
        }
        if (opt==3){
            scanf("%d%d",&L,&R);
            printf("%lld\n",ask(1,L,R)%p);  //求和
        }
    }
    return 0;
}

 

                                               ——时间划过风的轨迹,那个少年,还在等你。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值