线段树 - AHOI 2009 维护序列 - Gym 237040G

线段树 - AHOI 2009 维护序列 - Gym 237040G

老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成。

有长为 n 的数列,不妨设为 a1,a2,⋯,an。有如下三种操作形式:

把数列中的一段数全部乘一个值;
把数列中的一段数全部加一个值;
询问数列中的一段数的和,由于答案可能很大,你只需输出这个数模 P 的值。

第一行两个整数 n 和 P;

第二行含有 n 个非负整数,从左到右依次为 a1,a2,⋯,an;

第三行有一个整数 M,表示操作总数;

从第四行开始每行描述一个操作,输入的操作有以下三种形式:

操 作 1 : " 1   t   g   c " , 表 示 把 所 有 满 足 t ≤ i ≤ g 的 a i 改 为 a i × c ; 操作 1:"1 \ t \ g \ c",表示把所有满足 t≤i≤g 的 a_i 改为 a_i×c; 1"1 t g c"tigaiai×c
操 作 2 : " 2   t   g   c " , 表 示 把 所 有 满 足 t ≤ i ≤ g 的 a i 改 为 a i + c ; 操作 2:"2 \ t \ g \ c",表示把所有满足 t≤i≤g 的 a_i 改为 a_i+c; 2"2 t g c"tigaiai+c
操 作 3 : " 3   t   g " , 询 问 所 有 满 足 t ≤ i ≤ g 的 a i 的 和 模 P 的 值 。 操作 3:"3 \ t \ g",询问所有满足 t≤i≤g 的 a_i 的和模 P 的值。 3"3 t g"tigaiP

同一行相邻两数之间用一个空格隔开,每行开头和末尾没有多余空格。

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

Example

Input

7 43
1 2 3 4 5 6 7
5
1 2 5 5
3 2 4
2 3 7 9
3 1 3
3 4 7

Output

2
35
8

对 于 全 部 测 试 数 据 , 1 ≤ t ≤ g ≤ n , 0 ≤ c , a i ≤ 1 0 9 , 1 ≤ P ≤ 1 0 9 。 对于全部测试数据,1≤t≤g≤n,0≤c,a_i≤10^9,1≤P≤10^9。 1tgn,0c,ai109,1P109


分析:

由 于 存 在 区 间 乘 和 区 间 加 两 种 操 作 , 因 此 需 在 节 点 结 构 体 中 增 加 两 个 懒 标 记 m u l 和 a d d 。 由于存在区间乘和区间加两种操作,因此需在节点结构体中增加两个懒标记mul和add。 muladd

由 于 用 到 两 个 懒 标 记 , 在 做 修 改 操 作 时 , 为 了 代 码 的 简 洁 性 , 用 一 个 操 作 来 实 现 懒 标 记 m u l 和 a d d 的 修 改 。 由于用到两个懒标记,在做修改操作时,为了代码的简洁性,用一个操作来实现懒标记mul和add的修改。 muladd

为 了 方 便 代 码 实 现 , 我 们 规 定 先 进 行 乘 法 操 作 , 再 进 行 加 法 操 作 。 即 将 每 个 数 表 示 成 : a × b + c 的 形 式 。 为了方便代码实现,我们规定先进行乘法操作,再进行加法操作。即将每个数表示成:a×b+c的形式。 便:a×b+c

修 改 前 , 初 始 值 为 a , b = 1 , c = 0 , 修 改 后 得 到 ( a × b + c ) × d + e 。 修改前,初始值为a,b=1,c=0,修改后得到(a×b+c)×d+e。 ab=1c=0(a×b+c)×d+e

若 进 行 乘 法 操 作 , 则 e = 0 , 扩 大 d 倍 ; 若 进 行 加 法 操 作 , 则 d = 1 , 增 加 e 。 若进行乘法操作,则e=0,扩大d倍;若进行加法操作,则d=1,增加e。 e=0dd=1e

( a × b + c ) × d + e = ( a × b ) × d + ( c × d + e ) , 对 比 修 改 前 a × b + c , (a×b+c)×d+e=(a×b)×d+(c×d+e),对比修改前a×b+c, (a×b+c)×d+e=(a×b)×d+(c×d+e)a×b+c

懒 标 记 m u l i = m u l i − 1 × m u l , a d d i = a d d i − 1 × m u l + a d d , 其 中 m u l 和 a d d 是 需 要 修 改 的 具 体 数 值 。 懒标记mul_{i}=mul_{i-1}×mul,add_i=add_{i-1}×mul+add,其中mul和add是需要修改的具体数值。 muli=muli1×muladdi=addi1×mul+addmuladd

记 父 节 点 为 u , 左 右 孩 子 分 别 为 l e f t 和 r i g h t , 则 懒 标 记 的 传 递 过 程 为 : 记父节点为u,左右孩子分别为left和right,则懒标记的传递过程为: uleftright

① 、 l e f t . s u m = l e f t . s u m × u . m u l + ( l e f t . r − l e f t . l + 1 ) × u . a d d ①、left.sum=left.sum×u.mul+(left.r-left.l+1)×u.add left.sum=left.sum×u.mul+(left.rleft.l+1)×u.add

② 、 l e f t . m u l = l e f t . m u l × u . m u l ②、left.mul=left.mul×u.mul left.mul=left.mul×u.mul

③ 、 l e f t . a d d = l e f t . a d d × u . m u l + u . a d d ③、left.add=left.add×u.mul+u.add left.add=left.add×u.mul+u.add

其 中 , s u m 是 指 当 前 节 点 所 代 表 的 区 间 和 。 右 孩 子 r i g h t 同 理 。 其中,sum是指当前节点所代表的区间和。右孩子right同理。 sumright

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

#define ll long long

using namespace std;

const int N=1e5+10;

struct node
{
    int l,r;
    int sum,mul,add;
}tr[N*4];

int n,p,m;
int w[N];

// (a*b+c)*d+e = a*b*d + c*d+e
void cal(node &t,int mul,int add)
{
    t.sum=((ll)t.sum*mul+(ll)(t.r-t.l+1)*add)%p;
    t.mul=(ll)t.mul*mul%p;
    t.add=((ll)t.add*mul+add)%p;
}

void pushup(int u)
{
    tr[u].sum=(tr[u<<1].sum+tr[u<<1|1].sum)%p;
}

void pushdown(int u)
{
    cal(tr[u<<1],tr[u].mul,tr[u].add);
    cal(tr[u<<1|1],tr[u].mul,tr[u].add);
    tr[u].add=0,tr[u].mul=1;  //清空父节点懒标记
}

void build(int u,int l,int r)
{
    if(l==r) tr[u]={l,r,w[r],1,0};  
    else
    {
        tr[u]={l,r,0,1,0};  
        int mid=l+r>>1;
        build(u<<1,l,mid),build(u<<1|1,mid+1,r);
        pushup(u);
    }
}

void modify(int u,int l,int r,int mul,int add)
{
    if(l<=tr[u].l&&tr[u].r<=r) cal(tr[u],mul,add);
    else
    {
        pushdown(u);
        int mid=tr[u].l+tr[u].r>>1;
        if(l<=mid) modify(u<<1,l,r,mul,add);
        if(r>mid) modify(u<<1|1,l,r,mul,add);
        pushup(u);
    }
}

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

int main()
{
    scanf("%d%d",&n,&p);
    for(int i=1;i<=n;i++) scanf("%d",&w[i]);
    build(1,1,n);
    
    scanf("%d",&m);
    int op,l,r,d;
    while(m--)
    {
        scanf("%d%d%d",&op,&l,&r);
        if(op==1)
        {
            scanf("%d",&d);
            modify(1,l,r,d,0);
        }
        else if(op==2)
        {
            scanf("%d",&d);
            modify(1,l,r,1,d);
        }
        else printf("%d\n",query(1,l,r));
    }
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值