墨墨购买了一套 N 支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。
墨墨会像你发布如下指令:
Q L R 代表询问你从第 L 支画笔到第 R 支画笔中共有几种不同颜色的画笔。
R P Col 把第 P 支画笔替换为颜色 Col。
为了满足墨墨的要求,你知道你需要干什么了吗?
输入格式
第 1 行两个整数 N,M,分别代表初始画笔的数量以及墨墨会做的事情的个数。
第 2 行 N 个整数,分别代表初始画笔排中第 i 支画笔的颜色。
第 3 行到第 2+M 行,每行分别代表墨墨会做的一件事情,格式见题干部分。
输出格式
对于每一个 Query 的询问,你需要在对应的行中给出一个数字,代表第 L 支画笔到第 R 支画笔中共有几种不同颜色的画笔。
数据范围
1≤N,M≤10000,
修改操作不多于 1000 次,
所有的输入数据中出现的所有整数均大于等于 1 且不超过 106。
输入样例:
6 5
1 2 3 4 5 5
Q 1 4
Q 2 6
R 1 2
Q 1 4
Q 2 6
输出样例:
4
4
3
4
感觉这是到现在为止我遇到的最奇妙的算法了。
普通莫队大多是以改变问询顺序来实现时间复杂度的优化。但这种莫队无法应付需要修改的情况。
如果说普通莫队是在一维线上暴力,那么带修改莫队就是在二维平面上暴力。多出来的一维就是时间轴。
每次修改是一次时间的改变。例如针对N长数组有K次修改,设一个数组
f[N][K]
,f[i][j]
表示第j次修改时,第i个数的值,在执行询问时直接跳到该询问对应的j值即可。
当然,我们不需要真的开一个二维数组,因为空间不够。所以给每个问询赋一个时间戳,在处理这个问询时,如果当前时间小于问询时间戳,那么就遍历这两个时间之间的所有修改。如果当前时间大于询问时间戳,就进行还原,具体还原的方法在下面的代码里。
#include <iostream>
#include <algorithm>
#include <vector>
#include <cmath>
#include <cstring>
using namespace std;
const int N = 10010, S = 1000010;
int n, m, len;
int mq, mc; //询问操作和修改操作的数量
int w[N], cnt[S], ans[N];
struct Query { //所有的查询操作
int id, l, r, t; //时间戳
} q[N];
struct Modify { //所有的修改操作
int p, c; //将第p个数修改成c
} c[N];
int get(int x) {
return x / len;
}
bool cmp(const Query &a, const Query &b) {
int al = get(a.l), ar = get(a.r);
int bl = get(b.l), br = get(b.r);
if (al != bl)
return al < bl;
if (ar != br)
return ar < br;
return a.t < b.t;
}
void add(int x, int &res) {
if (!cnt[x])
res ++ ;
cnt[x] ++ ;
}
void del(int x, int &res) {
cnt[x] -- ;
if (!cnt[x])
res -- ;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ )
scanf("%d", &w[i]);
for (int i = 0; i < m; i ++ ) {
char op[2];
int a, b;
scanf("%s%d%d", op, &a, &b);
if (*op == 'Q')
mq ++, q[mq] = {mq, a, b, mc};
else
c[ ++ mc] = {a, b};
}
len = cbrt((double)n * mc) + 1;
sort(q + 1, q + mq + 1, cmp);
for (int i = 0, j = 1, t = 0, k = 1, res = 0; k <= mq; k ++ ) {
int id = q[k].id, l = q[k].l, r = q[k].r, tm = q[k].t;
while (i < r)
add(w[ ++ i], res);
while (i > r)
del(w[i -- ], res);
while (j < l)
del(w[j ++ ], res);
while (j > l)
add(w[ -- j], res);
//在基础的线性修改的情况下再加入一个时间轴修改
while (t < tm) {//如果当前时间小于该询问的时间,就要将这个期间的修改加上
t ++ ;//当前时间的修改也要加上
//p:第p个数 c修改成为的值
if (c[t].p >= j && c[t].p <= i) {
del(w[c[t].p], res);
add(c[t].c, res);
}
swap(w[c[t].p], c[t].c);//将w中的数也完成修改。
//当时间逆流时能实现数据恢复,c[t].c储存着当时的数据
}
while (t > tm) {//当当前时间大于询问时间,逆流,沿途进行数据恢复
if (c[t].p >= j && c[t].p <= i) {
del(w[c[t].p], res);
add(c[t].c, res);
}
swap(w[c[t].p], c[t].c);
t -- ;
}
ans[id] = res;
}
for (int i = 1; i <= mq; i ++ )
printf("%d\n", ans[i]);
return 0;
}