【BZOJ】1798 [Ahoi2009]Seq 维护序列seq

Description

老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成。 有长为 N 的数列,不妨设为a1,a2,,aN 。有如下三种操作形式:
(1)把数列中的一段数全部乘一个值;
(3)询问数列中的一段数的和,由于答案可能很大,你只需输出这个数模 P 的值。

Input

第一行两个整数N P(1P1000000000
第二行含有 N 个非负整数,从左到右依次为a1,a2,,aN,(0ai1000000000,1iN)
第三行有一个整数 M ,表示操作总数。
从第四行开始每行描述一个操作,输入的操作有以下三种形式:
操作1:“1 t g c”(不含双引号)。表示把所有满足tig ai 改为 ai×c(1tgN,0c1000000000)
操作2:“2 t g c”(不含双引号)。表示把所有满足 tig ai 改为 ai+c(1tgN,0c1000000000)
操作3:“3 t g”(不含双引号)。询问所有满足 tig ai 的和模 P 的值 (1tgN)。 同一行相邻两数之间用一个空格隔开,每行开头和末尾没有多余空格。

Output

对每个操作3,按照它在输入中出现的顺序,依次输出一行一个整数表示询问结果。

Solution

很经典的线段树题目。这题的题点就在于乘法标记和加法标记的叠加。
假如规定一段区间同时有两个标记的时候是先乘后加,假设一段区间,值为a,乘法标记为mul,加法标记为add。
那么其真实值就为 a×mul+add
现在来了一个新的加法标记 add ,那么区间值就是 a×mul+add+add
如果来了一个新的乘法标记 mul ,那么区间值就是 a×mul×mul+add×mul
那么这样就很明显了,如果是加法,直接叠加就好,如果是乘法,就把乘法和加法标记同时乘上修改量即可。

#include<stdio.h>
#include<cstring>
#define M 131072

typedef long long ll;

int t[(M<<1)+2],tag[(M<<1)+2],a[(M<<1)+2],c,L,R,aim,ans,n,q,mod;

inline void maintain(const int &k,const int &l,const int &r)
{
    if (t[k]==1 && tag[k]==0) return;
    a[k]=((ll)a[k]*t[k]+(ll)tag[k]*(r-l+1)) % mod;
    if (l==r) {tag[k]=0,t[k]=1;return;}
    t[k<<1]=(ll)t[k]*t[k<<1] % mod,t[k<<1|1]=(ll)t[k<<1|1]*t[k] % mod;
    tag[k<<1]=(ll)tag[k<<1]*t[k] % mod,tag[k<<1|1]=(ll)tag[k<<1|1]*t[k] % mod;
    tag[k<<1]=(tag[k<<1]+tag[k]) % mod,tag[k<<1|1]=(tag[k<<1|1]+tag[k]) % mod;
    tag[k]=0,t[k]=1;
}

void mul(const int &k,const int &l,const int &r)
{
    maintain(k,l,r);
    if (L<=l && r<=R){t[k]=(ll)t[k]*aim % mod;return;}
    int mid=(l+r)>>1;
    if (L<=mid) mul(k<<1,l,mid);
    if (mid<R) mul(k<<1|1,mid+1,r);
    maintain(k<<1,l,mid);
    maintain(k<<1|1,mid+1,r);
    a[k]=(a[k<<1]+a[k<<1|1]) % mod;
}

void add(const int &k,const int &l,const int &r)
{
    maintain(k,l,r);
    if (L<=l && r<=R){tag[k]=(tag[k]+aim) % mod;return;}
    int mid=(l+r)>>1;
    if (L<=mid) add(k<<1,l,mid);
    if (mid<R) add(k<<1|1,mid+1,r);
    maintain(k<<1,l,mid);
    maintain(k<<1|1,mid+1,r);
    a[k]=(a[k<<1]+a[k<<1|1]) % mod;
}

void get(const int &k,const int &l,const int &r)
{
    maintain(k,l,r);
    if (L<=l && r<=R)
    {
        if (l!=r)
        {
            int mid=(l+r)>>1;
            maintain(k<<1,l,mid);
            maintain(k<<1|1,mid+1,r);
            a[k]=(a[k<<1]+a[k<<1|1]) % mod;
        }
        ans=(ans+a[k]) % mod;
        return;
    }
    int mid=(l+r)>>1;
    if (L<=mid) get(k<<1,l,mid);
    if (mid<R) get(k<<1|1,mid+1,r);
}

int main()
{
    scanf("%d%d",&n,&mod);
    for (int i=0;i<n;i++) scanf("%d",a+M+i),a[M+i]%=mod;
    for (int i=M-1;i;i--) a[i]=(a[i<<1]+a[i<<1|1]) % mod;
    for (int i=1;i<M<<1;i++) t[i]=1;
    scanf("%d",&q);
    while (q--)
    {
        scanf("%d%d%d",&c,&L,&R);
        if (c==1) scanf("%d",&aim),aim%=mod,mul(1,1,M);
        if (c==2) scanf("%d",&aim),aim%=mod,add(1,1,M);
        if (c==3) ans=0,get(1,1,M),printf("%d\n",ans);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值