# [国家集训队] 数颜色 / 维护队列
## 题目描述
墨墨购买了一套 N 支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。墨墨会向你发布如下指令:
1. Q L R 代表询问你从第 L 支画笔到第 R 支画笔中共有几种不同颜色的画笔。
2. R P C 把第 P 支画笔替换为颜色 C。
为了满足墨墨的要求,你知道你需要干什么了吗?
## 输入格式
第 1 行两个整数 N,M,分别代表初始画笔的数量以及墨墨会做的事情的个数。
第 2 行 N 个整数,分别代表初始画笔排中第 $i$ 支画笔的颜色。
第 3 行到第 2+M 行,每行分别代表墨墨会做的一件事情,格式见题干部分。
## 输出格式
对于每一个 Query 的询问,你需要在对应的行中给出一个数字,代表第 $L$ 支画笔到第 $R$ 支画笔中共有几种不同颜色的画笔。
## 样例 #1
### 样例输入 #1
6 5
1 2 3 4 5 5
Q 1 4
Q 2 6
R 1 2
Q 1 4
Q 2 6
```
### 样例输出 #1
```
4
4
3
4
```
本题是一道待修改莫队的模板题。 非常折磨人尤其是像我这样刚学莫队的,而且我还遇到了一个究极玄学的问题。
代码如下:
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
const int MAX_NM = 133333 + 5;
const int MAX_COLOR = 1000000 + 5;
int n, m, siz, bnum, nowans, cntq, cntm;
int c[MAX_NM], cnt[MAX_COLOR], belong[MAX_NM], ans[MAX_NM];// !!!!! 该地方的后面两个数组如
// 果换位置的话题目的后三个测试点会出错,这里的原因是为什么我也不清楚,挺玄学的(如果有大佬知道,
// 非常欢迎评论区解惑)
struct query {
int l, r, tim, id; // f 作为修改位当f = 1时对c里面的颜色进行修改(默认为0)
} q[MAX_NM];
struct modify {
int pos, color, last;
} mod[MAX_NM];
int read() {
int x = 0;
char c = getchar();
while (c < '0' || c > '9') c = getchar();
while (c >= '0' && c <= '9') {
x = (x << 3) + (x << 1) + (c ^ '0');
c = getchar();
}
return x;
}
void write(int x) {
if (x / 10) write(x / 10);
// 123 123%10 = 3 12 % 10 = 2;
putchar(x % 10 + '0');
return;
}
/*
int cmp(query a, query b) {
return (belong[a.l] ^ belong[b.l]) ? belong[a.l] < belong[b.l] :
((belong[a.r] ^ belong[b.r]) ? belong[a.r] < belong[b.r] : a.time < b.time);
}
*/
// 第一关键字 左端点所属块 第二关键字 右端点所属块 第三关键字 询问的时间戳(time)
int cmp(query a, query b) {
return (belong[a.l] ^ belong[b.l]) ? belong[a.l] < belong[b.l] :
((belong[a.r] ^ belong[b.r]) ? belong[a.r] < belong[b.r] : a.tim < b.tim);
}
int main() {
n = read();
m = read();
char opt;
siz = pow(n, 2.0 / 3);
bnum = ceil((double)n / siz);
for (int i = 1; i <= bnum; i++) {
for (int j = (i - 1) * siz + 1; j <= i * siz; j++) {
belong[j] = i;
}
}
for (int i = 1; i <= n; i++) c[i] = read();
for (int i = 1; i <= m; i++) {
cin >> opt;
// 这个位置opt除了cin之外 还可以用scanf输入一个opt数组(%s) 后面的if语句则可以
// if(opt[0] == 'Q') 但是不能scanf一个字符(%c)
// 此处原因我也不懂,想去也没头绪,不知道找哪个方向。 有大佬路过欢迎指点(指点指点吧,帮帮孩子)
if (opt == 'Q') {
q[++cntq].l = read();
q[cntq].r = read();
q[cntq].tim = cntm;
q[cntq].id = cntq;
} else if (opt == 'R') {
mod[++cntm].pos = read();
mod[cntm].color = read();
}
}
sort(q + 1, q + 1 + cntq, cmp);
int l = 1, r = 0, t = 0;
for (int i = 1; i <= cntq; i++) {
int ql = q[i].l, qr = q[i].r, qt = q[i].tim;
while (l < ql) nowans -= !--cnt[c[l++]];
while (l > ql) nowans += !cnt[c[--l]]++;
while (r < qr) nowans += !cnt[c[++r]]++;
while (r > qr) nowans -= !--cnt[c[r--]];
/*
从1位置的时间戳 到 当前询问所对应的时间戳
每次将nowans的值进行改变 将原位置的颜色在 cnt 数组中的值减一, 减完后值为0-->nowans--
将修改后的颜色在 cnt 数组中的值加一, 先判断值是否为0-->nowans++
*/
while (t < qt) {
++t;
// 区间内待修改位置原来的颜色对应的出现次数减少一次后判断其是否为0 为0则得1 否则得0
// 修改后的颜色所对应的出现次数先判断其是否为0 为0得1 否则得0 之后进行出现次数加1操作
if (ql <= mod[t].pos && mod[t].pos <= qr) nowans -= !--cnt[c[mod[t].pos]] - !cnt[mod[t].color]++;
// 化简操作(易理解): nowans -= !--cnt[c[mod[time].pos]];
// nowans += !cnt[mod[time].color]++;
swap(c[mod[t].pos], mod[t].color);
}
while (t > qt) {
// 该循环所要进行的操作是将修改后的颜色进行复原
// 由于前面的swapp 将mod数组中 修改后的颜色与修改前的颜色进行了交换, 所以操作可变化为
// 将修改后的颜色视为未修改的颜色, 然后对其进行修改 即可将颜色进行复原
// 区间内待修改位置原来的颜色对应的出现次数减少一次后判断其是否为0 为0则得1 否则得0
// 修改后的颜色所对应的出现次数先判断其是否为0 为0得1 否则得0 之后进行出现次数加1操作
if (ql <= mod[t].pos && mod[t].pos <= qr) nowans -= !--cnt[c[mod[t].pos]] - !cnt[mod[t].color]++;
// 化简操作(易理解): nowans -= !--cnt[c[mod[time].pos]];
// nowans += !cnt[mod[time].color]++;
swap(c[mod[t].pos], mod[t].color);
t--;
}
ans[q[i].id] = nowans;
}
for (int i = 1; i <= cntq; i++) {
write(ans[i]);
putchar('\n');
}
return 0;
}