AcWing 1277. 维护序列

\(AcWing\) \(1277\). 维护序列

一、题目大意

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

有长为 \(N\) 的数列,不妨设为 \(a_1,a_2,…,a_N\)

有如下三种操作形式:

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

输入格式
第一行两个整数 \(N\)\(P\)

第二行含有 \(N\) 个非负整数,从左到右依次为 \(a_1,a_2,…,a_N\)

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

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

  • 操作 \(1\)1 t g c,表示把所有满足 \(t≤i≤g\)\(a_i\) 改为 \(a_i×c\)
  • 操作 \(2\)2 t g c,表示把所有满足 \(t≤i≤g\)\(a_i\) 改为 \(a_i+c\)
  • 操作 \(3\)3 t g,询问所有满足 \(t≤i≤g\)\(a_i\) 的和模 \(P\) 的值。

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

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

数据范围
\(1≤N,M≤10^5, \\ 1≤t≤g≤N, \\ 0≤c,ai≤10^9,\\ 1≤P≤10^9\)

输入样例:

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\) 次操作后,数列为 {\(1,10,15,20,25,6,7\)};

对第 \(2\) 次操作,和为 \(10+15+20=45\),模 \(43\) 的结果是 \(2\)

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

对第 \(4\) 次操作,和为 \(1+10+24=35\),模 \(43\) 的结果是 \(35\)

对第 \(5\) 次操作,和为 \(29+34+15+16=94\),模 \(43\) 的结果是 \(8\)

二、解题思路

首先考虑线段树结构体需要存储哪些信息:

区间信息 \(l\) \(r\)
区间和 \(sum\)
懒标记 \(add\) \(mul\)

然后考虑懒标记的更新,有两种优先级:

  • 先加法再乘法,即\((x+a)*b\),此时再进行一次乘法操作得到\((x+a)*b*c\)是可以维护乘\((x+a)*b\)的形式的,但如果进行一次加法操作\((x+a)*b+c\)就不好维护成\((x+a)*b\)的形式了。

  • 先乘法再加法,即\((x*a)+b\),此时再进行一次乘法操作\((x*a+b)*c=(x*a*c)+b*c\),进行一次加法操作\((x*a+b)+c=x*a+b+c\),都可以维护成\((x+a)*b\)的形式。

所以选择第二种优先级表达,即先乘法再加法,与算数运算一致。

接下来考虑操作,不妨把加法和乘法看作一次基础操作,即通通化为\((x*a+b)\),加法令\(a=1\),乘法令\(b=0\)。所以\((x*a+b)*c+d=x*a*c+b*c+d\)

void eval(Node &t, LL add, LL mul){
    t.sum = (t.sum * mul+(t.r-t.l+1)*add) % p ;// 先更新区间和信息
    t.mul = t.mul * t.mul % p;
    t.add = (t.add * mul + add ) % p;
}

然后再在\(pushdown\)中对两个儿子调用这个基础操作。

三、实现代码

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

using namespace std;
typedef long long LL;

//线段树宏定义
#define ls u << 1
#define rs u << 1 | 1

const int N = 100010;

// n:N 个非负整数   p:数模 P 的值  m:操作总数
int n, p, m;
int w[N];

struct Node {
    int l, r;
    LL sum, add, mul;
} tr[N << 2];

void pushup(int u) {
    tr[u].sum = (tr[u << 1].sum + tr[u << 1 | 1].sum) % p; //更新sum和信息
}

//计算函数
void eval(Node &t, LL add, LL mul) {
    t.sum = (t.sum * mul + (t.r - t.l + 1) * add) % p;
    t.mul = t.mul * mul % p;
    t.add = (t.add * mul + add) % p;
}

void pushdown(int u) {
    eval(tr[ls], tr[u].add, tr[u].mul); //处理左儿子
    eval(tr[rs], tr[u].add, tr[u].mul); //处理右儿子
    tr[u].add = 0, tr[u].mul = 1;       //清空懒标记
}

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

void modify(int u, int l, int r, int add, int mul) {
    if (tr[u].l >= l && tr[u].r <= r) {
        eval(tr[u], add, mul);
        return;
    }

    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    if (l <= mid) modify(ls, l, r, add, mul);
    if (r > mid) modify(rs, l, r, add, mul);
    pushup(u);
}

int 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;
    int sum = 0;
    if (l <= mid) sum = query(ls, l, r);
    if (r > mid) sum = (sum + query(rs, l, r)) % p;
    return sum;
}

int main() {
    //加快读入
    ios::sync_with_stdio(false), cin.tie(0);

    cin >> n >> p;
    for (int i = 1; i <= n; i++) cin >> w[i];

    //构建线段树,root=1,l=1,r=n
    build(1, 1, n);

    cin >> m;
    while (m--) {
        int t, l, r, d;
        cin >> t >> l >> r;
        if (t == 1) { //乘
            cin >> d;
            modify(1, l, r, 0, d);
        } else if (t == 2) { //加
            cin >> d;
            modify(1, l, r, d, 1);
        } else //查
            printf("%d\n", query(1, l, r));
    }
    return 0;
}
posted @ 2022-04-19 13:32  糖豆爸爸  阅读(75)  评论(0编辑  收藏  举报
Live2D