Files
python/TangDou/AcWing/SegmentTree/1277.md
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

5.4 KiB
Raw Blame History

##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)bc$是可以维护乘$(x+a)*b$的形式的,但如果进行一次加法操作$(x+a)*b+c$就不好维护成$(x+a)*b$的形式了。

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

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

接下来考虑操作,不妨把加法和乘法看作一次基础操作,即通通化为$(xa+b)$,加法令$a=1$,乘法令$b=0$。所以$(xa+b)c+d=xac+bc+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;
}