P1903 [国家集训队] 数颜色 / 维护队列 单点修改莫队
解题思路
问题分析
本题需要处理两种操作:
-
查询区间内不同颜色的数量(Q操作)
-
修改某个位置的颜色(R操作)
这是一个典型的带修改的区间查询问题,适合使用带修改的莫队算法(也称为三维莫队)。
算法选择
普通莫队算法只能处理静态查询,而本题有修改操作,因此需要使用带修改的莫队。该算法在普通莫队的基础上增加了时间维度,将每个查询和修改都打上时间戳,处理查询时需要考虑时间的影响。
关键步骤
-
分块处理:将序列分成若干块,块大小为n^(2/3),这是带修改莫队的最优分块大小。
-
查询排序:按照左端点所在块、右端点所在块、时间戳的顺序排序查询。
-
指针移动:
-
时间指针:处理修改操作,保证查询时处于正确的时间点
-
左右指针:维护当前区间内的颜色统计
-
-
颜色统计:使用计数数组维护当前区间内各颜色的出现次数,动态维护不同颜色的数量
时间复杂度
-
排序:O(mlogm)
-
处理查询:O(mn^(2/3))
整体复杂度为O(mn^(2/3)),能够通过题目给定的数据规模。
注意事项
-
修改操作需要记录旧颜色,以便回滚
-
处理查询时要先处理时间维度,再处理空间维度
-
块大小的选择直接影响算法效率,n^(2/3)是最优选择
该算法巧妙地通过分块和指针移动,将复杂的三维问题(左、右、时间)转化为高效的顺序处理,是处理带修改区间查询问题的经典方法。
#include<bits/stdc++.h> using namespace std; const int N = 1e6 + 10; // 查询结构体:记录查询的左右边界、查询编号和时间戳 struct query{ int l,r,id,t; }; query q[N]; // 修改结构体:记录修改的位置、新颜色和旧颜色 struct change{ int pos,col,pre; }; change u[N]; int n,m,blk,qcnt,ucnt; // 画笔数量、操作数、块大小、查询数、修改数 int a[N],cnt[N],ans[N]; // 颜色数组、颜色计数数组、答案数组 int now,L = 1,R,T; // 当前答案、左指针、右指针、时间指针 // 查询排序比较函数:先按左块排序,再按右块排序,最后按时间排序 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; } // 删除颜色:减少计数,如果减到0则减少答案 void del(int col) { cnt[col]--; if(cnt[col] == 0) now--; } // 添加颜色:增加计数,如果从0到1则增加答案 void add(int col) { cnt[col]++; if(cnt[col] == 1) now++; } // 应用修改:如果在当前区间内,先删除旧颜色再添加新颜色 void apply(int pos,int col) { if(L <= pos && pos <= R) { del(a[pos]); add(col); } a[pos] = col; } int main() { scanf("%d%d",&n,&m); blk = pow(n,2.0/3); // 计算块大小,n的2/3次方 // 读入初始颜色 for(int i = 1; i <= n; i++) scanf("%d",&a[i]); // 处理操作 for(int i = 1; i <= m; i++) { char op[2]; int l,r,p,c; scanf("%s",op); if(op[0] == 'Q'){ // 查询操作 scanf("%d%d",&l,&r); q[++qcnt] = {l,r,qcnt,ucnt}; // 记录查询和时间戳 } if(op[0] == 'R'){ // 修改操作 scanf("%d%d",&p,&c); u[++ucnt] = {p,c,a[p]}; // 记录修改和旧颜色 a[p] = c; // 临时修改颜色 } } // 恢复初始颜色(因为前面临时修改了) 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].col); } 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] = now; // 记录答案 } // 输出所有查询结果 for(int i = 1; i <= qcnt; i++) printf("%d\n",ans[i]); return 0; }

浙公网安备 33010602011771号