You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

5.4 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

##AcWing 1277. 维护序列

一、题目大意

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

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

有如下三种操作形式:

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

输入格式 第一行两个整数 NP

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

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

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

  • 操作 11 t g c,表示把所有满足 t≤i≤ga_i 改为 a_i×c
  • 操作 22 t g c,表示把所有满足 t≤i≤ga_i 改为 a_i+c
  • 操作 33 t g,询问所有满足 t≤i≤ga_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;
}