P2464 [SDOI2008] 郁闷的小 J 带修莫队
解题思路
问题分析
本题需要处理两种操作:
-
修改操作(C):将某个位置的书籍编码替换为新编码
-
查询操作(Q):查询区间内特定编码的书籍数量
这是一个典型的带修改的区间查询问题,适合使用带修改的莫队算法(也称为三维莫队)。
算法选择
-
普通莫队:只能处理静态查询
-
带修改莫队:增加时间维度,处理动态修改
-
离散化:由于编码值范围很大(≤2³¹-1),需要先离散化
关键步骤
-
离散化处理:
-
收集所有出现的编码值(初始数据、修改值、查询值)
-
排序并去重后,将原始编码映射到连续的整数
-
-
分块处理:
-
块大小为
n^(2/3),这是带修改莫队的最优分块大小
-
-
查询排序:
-
按左端点所在块、右端点所在块、时间戳的顺序排序
-
-
指针移动:
-
时间指针:处理修改操作,保证查询时处于正确的时间点
-
左右指针:维护当前区间内的编码统计
-
-
答案统计:
-
使用
cnt数组记录当前区间内各编码的出现次数 -
直接返回
cnt[q[i].k]作为查询结果
-
复杂度分析
-
离散化:O((n + m) log (n + m))
-
莫队算法:O(n^(5/3))(带修改莫队的典型复杂度)
-
可以通过题目给定的数据规模(n, m ≤ 1e5)
注意事项
-
修改操作需要记录旧编码值,以便回滚
-
离散化后所有编码值变为连续的整数,方便统计
-
处理查询时要先处理时间维度,再处理空间维度
代码注释
#include<bits/stdc++.h> #define ll long long using namespace std; const int N = 2e5 + 10; // 查询结构体:记录查询的左右边界、查询的编码值、查询编号和时间戳 struct query { int l, r, k, id, t; }; query q[N]; // 修改结构体:记录修改的位置、新编码值和旧编码值 struct change { int pos, num, pre; }; change u[N]; int n, m, blk, a[N], cnt[N], ans[N]; vector<int> b; // 离散化数组 int qcnt, ucnt, sum, L = 1, R, T; // 查询数、修改数、当前答案、左右指针、时间指针 // 离散化函数:返回x在b数组中的排名(从1开始) int get(int x) { return lower_bound(b.begin(), b.end(), x) - b.begin() + 1; } // 莫队排序比较函数:先按左块排序,再按右块排序,最后按时间排序 bool cmp(query a, query b) { if (a.l / blk != b.l / blk) return a.l < b.l; if (a.r / blk != b.r / blk) return a.r < b.r; return a.t < b.t; } // 增加编码出现次数 void add(int num) { cnt[num]++; } // 减少编码出现次数 void del(int num) { cnt[num]--; } // 应用修改:如果在当前区间内,先删除旧编码再添加新编码 void apply(int pos, int num) { if (L <= pos && pos <= R) { del(a[pos]); add(num); } a[pos] = num; } int main() { scanf("%d%d", &n, &m); blk = pow(n, 2.0 / 3); // 计算块大小 // 读取初始编码并收集离散化数据 for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); b.push_back(a[i]); } // 处理操作 for (int i = 1; i <= m; i++) { char op[2]; scanf("%s", op); if (op[0] == 'Q') { // 查询操作 int l, r, k; scanf("%d%d%d", &l, &r, &k); q[++qcnt] = {l, r, k, qcnt, ucnt}; // 记录查询和时间戳 b.push_back(k); // 收集查询的编码值 } if (op[0] == 'C') { // 修改操作 int pos, p; scanf("%d%d", &pos, &p); u[++ucnt] = {pos, p, a[pos]}; // 记录修改和旧编码值 a[pos] = p; // 临时修改 b.push_back(p); // 收集新编码值 } } // 离散化处理 sort(b.begin(), b.end()); b.erase(unique(b.begin(), b.end()), b.end()); // 将原始数据、查询和修改中的编码值替换为离散化后的值 for (int i = 1; i <= n; i++) a[i] = get(a[i]); for (int i = 1; i <= qcnt; i++) q[i].k = get(q[i].k); for (int i = 1; i <= ucnt; i++) { u[i].num = get(u[i].num); u[i].pre = get(u[i].pre); } // 恢复初始数组(因为前面临时修改过) for (int i = ucnt; i >= 1; i--) { a[u[i].pos] = u[i].pre; } // 莫队算法处理查询 sort(q + 1, q + 1 + qcnt, cmp); for (int i = 1; i <= qcnt; i++) { // 处理时间维度:应用或撤销修改 while (T < q[i].t) { T++; apply(u[T].pos, u[T].num); } while (T > q[i].t) { apply(u[T].pos, u[T].pre); T--; } // 处理空间维度:移动左右指针 while (L < q[i].l) del(a[L++]); while (L > q[i].l) add(a[--L]); while (R < q[i].r) add(a[++R]); while (R > q[i].r) del(a[R--]); ans[q[i].id] = cnt[q[i].k]; // 记录当前查询的答案 } // 输出所有查询结果 for (int i = 1; i <= qcnt; i++) { printf("%d\n", ans[i]); } return 0; }

浙公网安备 33010602011771号