维护序列 --- 线段树懒标计高级应用

时/空限制: 1s / 64MB

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

有长为 NN 的数列,不妨设为 a1,a2,…,aNa1,a2,…,aN 。

有如下三种操作形式:

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

输入格式

第一行两个整数 NN 和 PP ;

第二行含有 NN 个非负整数,从左到右依次为 a1,a2,…,aNa1,a2,…,aN ;

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

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

  • 操作 11 :1 t g c,表示把所有满足 t≤i≤gt≤i≤g 的 aiai 改为 ai×cai×c ;
  • 操作 22 :2 t g c,表示把所有满足 t≤i≤gt≤i≤g 的 aiai 改为 ai+cai+c ;
  • 操作 33 :3 t g,询问所有满足 t≤i≤gt≤i≤g 的 aiai 的和模 PP 的值。

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

输出格式

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

数据范围

1≤N,M≤1051≤N,M≤105 ,
1≤t≤g≤N1≤t≤g≤N ,
0≤c,ai≤1090≤c,ai≤109 ,
1≤P≤1091≤P≤109

输入样例:

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

输出样例:

2
35
8

样例解释

初始时数列为 {1,2,3,4,5,6,7}{1,2,3,4,5,6,7} ;

经过第 11 次操作后,数列为 {1,10,15,20,25,6,7}{1,10,15,20,25,6,7} ;

对第 22 次操作,和为 10+15+20=4510+15+20=45 ,模 4343 的结果是 22 ;

经过第 33 次操作后,数列为 {1,10,24,29,34,15,16}{1,10,24,29,34,15,16} ;

对第 44 次操作,和为 1+10+24=351+10+24=35 ,模 4343 的结果是 3535 ;

对第 55 次操作,和为 29+34+15+16=9429+34+15+16=94 ,模 4343 的结果是 88 。

思路: 

利用懒标计维护两个值:累加值和累乘值,关键点在于如何更新这两个值,看代码。

#include<iostream>
#define ll long long
using namespace std;

const int N = 100000 + 10;

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

ll a[N];
int n,p,m;

void pushup(node &u, node &l, node &r)
{
    u.sum = (l.sum + r.sum) % p;
}

void pushup(int u)
{
    pushup(tr[u], tr[u<<1], tr[u<<1|1]);
}

void eval(node &u, ll add, ll mul)
{
    u.sum = (u.sum * mul + (u.r - u.l + 1) * add) % p;
    u.mul = (mul * u.mul) % p;
    u.add = (u.add * mul + add) % p;
}

void pushdown(int u)//使用懒标计更新子节点
{
    eval(tr[u<<1], tr[u].add, tr[u].mul);
    eval(tr[u<<1|1], tr[u].add, tr[u].mul);
    tr[u].add = 0;
    tr[u].mul = 1;
}

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

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

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


int main()
{
    cin>>n>>p;
    for(int i=1; i<=n; i++)
        scanf("%lld",&a[i]);
    build(1, 1, n);
    int c;
    cin>>m;
    while(m--)
    {
        cin>>c;
        if(c == 1)
        {
            int l,r;
            ll v;
            cin>>l>>r>>v;
            modify(1, l, r, 0, v);
        }
        else if(c == 2)
        {
            int l,r;
            ll v;
            cin>>l>>r>>v;
            modify(1, l, r, v, 1);
        }
        else
        {
            int l, r;
            cin>>l>>r;
            cout<<query(1, l, r)<<endl;
        }
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值