P2023 [AHOI2009] 维护序列
解题思路
这道题需要使用线段树来高效处理区间操作和查询。由于同时存在区间加法和区间乘法操作,我们需要设计一种能够同时处理这两种操作的延迟标记(lazy tag)机制。
关键点:
-
双标记处理:需要同时维护乘法标记和加法标记,并正确处理它们的优先级关系(乘法优先于加法)。
-
标记下传:在访问子节点前,必须将当前节点的标记下传,确保子节点的值是正确的。
-
标记合并:当新的操作到来时,需要正确合并新标记与已有标记。
处理顺序:
-
乘法操作会影响已有的加法标记和乘法标记
-
加法操作只影响加法标记
-
下传标记时,先处理乘法标记,再处理加法标记
代码注释
#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; }

浙公网安备 33010602011771号