P2023 [AHOI2009] 维护序列

解题思路

这道题需要使用线段树来高效处理区间操作和查询。由于同时存在区间加法和区间乘法操作,我们需要设计一种能够同时处理这两种操作的延迟标记(lazy tag)机制。

关键点:

  1. 双标记处理:需要同时维护乘法标记和加法标记,并正确处理它们的优先级关系(乘法优先于加法)。

  2. 标记下传:在访问子节点前,必须将当前节点的标记下传,确保子节点的值是正确的。

  3. 标记合并:当新的操作到来时,需要正确合并新标记与已有标记。

处理顺序:

  1. 乘法操作会影响已有的加法标记和乘法标记

  2. 加法操作只影响加法标记

  3. 下传标记时,先处理乘法标记,再处理加法标记

代码注释

#include<bits/stdc++.h>
#define lc rt << 1      // 左子节点索引
#define rc rt << 1 | 1   // 右子节点索引
#define lson lc,l,mid    // 左子树参数
#define rson rc,mid + 1,r // 右子树参数
#define ll long long     // 定义长整型别名
using namespace std;
const int N = 2e5 + 10, inf = 0x3f3f3f3f; // 定义常量

// 线段树节点结构体
struct node {
    ll sum, alazy, mlazy; // 区间和、加法懒标记、乘法懒标记
};
node t[N << 2]; // 线段树数组
int n, m, mod;  // 元素个数、操作次数、模数
int a[N];       // 原始数组

// 更新当前节点的sum值为左右子节点sum之和
void pushup(int rt) {
    t[rt].sum = (t[lc].sum + t[rc].sum) % mod;
}

// 下传标记函数
void down(int rt, int l, int r, ll add, ll mul) {
    // 更新当前节点的sum值:先乘后加
    t[rt].sum = (t[rt].sum * mul % mod + (r - l + 1) * add % mod) % mod;
    // 更新加法标记:先乘后加
    t[rt].alazy = ((t[rt].alazy * mul) % mod + add) % mod;
    // 更新乘法标记:直接相乘
    t[rt].mlazy = (t[rt].mlazy * mul) % mod;
}

// 下传当前节点的标记到子节点
void pushdown(int rt, int l, int r) {
    // 如果没有标记则直接返回
    if (t[rt].alazy == 0 && t[rt].mlazy == 1) return;
    int mid = (l + r) / 2;
    // 下传标记到左子树
    down(lson, t[rt].alazy, t[rt].mlazy);
    // 下传标记到右子树
    down(rson, t[rt].alazy, t[rt].mlazy);
    // 重置当前节点的标记
    t[rt].alazy = 0;
    t[rt].mlazy = 1;
}

// 构建线段树
void build(int rt, int l, int r) {
    // 初始化标记
    t[rt].alazy = 0;
    t[rt].mlazy = 1;
    // 如果是叶子节点
    if (l == r) {
        t[rt].sum = a[l]; // 直接赋值
        return;
    }
    int mid = (l + r) / 2;
    // 递归构建左右子树
    build(lson);
    build(rson);
    // 更新当前节点
    pushup(rt);
}

// 区间加法操作
void change_add(int rt, int l, int r, int x, int y, ll z) {
    // 如果当前区间与目标区间无交集
    if (r < x || y < l) return;
    // 如果当前区间完全包含在目标区间内
    if (x <= l && r <= y) {
        // 更新sum和加法标记
        t[rt].sum = (t[rt].sum + (r - l + 1) * z % mod) % mod;
        t[rt].alazy = (t[rt].alazy + z) % mod;
        return;
    }
    int mid = (l + r) / 2;
    // 下传标记
    pushdown(rt, l, r);
    // 递归处理左右子树
    change_add(lson, x, y, z);
    change_add(rson, x, y, z);
    // 更新当前节点
    pushup(rt);
}

// 区间乘法操作
void change_mul(int rt, int l, int r, int x, int y, ll z) {
    // 如果当前区间与目标区间无交集
    if (r < x || y < l) return;
    // 如果当前区间完全包含在目标区间内
    if (x <= l && r <= y) {
        // 更新sum、加法标记和乘法标记
        t[rt].sum = (t[rt].sum * z) % mod;
        t[rt].alazy = (t[rt].alazy * z) % mod;
        t[rt].mlazy = (t[rt].mlazy * z) % mod;
        return;
    }
    int mid = (l + r) / 2;
    // 下传标记
    pushdown(rt, l, r);
    // 递归处理左右子树
    change_mul(lson, x, y, z);
    change_mul(rson, x, y, z);
    // 更新当前节点
    pushup(rt);
}

// 区间查询操作
ll query(int rt, int l, int r, int x, int y) {
    // 如果当前区间与目标区间无交集
    if (r < x || y < l) return 0;
    // 如果当前区间完全包含在目标区间内
    if (x <= l && r <= y) return t[rt].sum % mod;
    int mid = (l + r) / 2;
    // 下传标记
    pushdown(rt, l, r);
    // 递归查询左右子树并求和
    return (query(lson, x, y) + query(rson, x, y)) % mod;
}

int main() {
    // 读取输入数据
    cin >> n >> mod;
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    // 构建线段树
    build(1, 1, n);
    cin >> m;
    // 处理每个操作
    while (m--) {
        int op;
        ll x, y, z;
        scanf("%d%lld%lld", &op, &x, &y);
        if (op == 1) { // 区间乘法
            scanf("%lld", &z);
            change_mul(1, 1, n, x, y, z);
        }
        if (op == 2) { // 区间加法
            scanf("%lld", &z);
            change_add(1, 1, n, x, y, z);
        }
        if (op == 3) { // 区间查询
            printf("%lld\n", query(1, 1, n, x, y));
        }
    }
    return 0;
}

 

posted @ 2025-06-08 09:15  CRt0729  阅读(9)  评论(0)    收藏  举报