数颜色 / 维护队列
题目链接:ybt金牌导航6-3-4 / luogu P1903
题目大意
给你一个序列,要你支持一些操作:
把一个数换成另一个数,查询一个区间中有多少个不同的数。
思路
我们考虑用分块来做。
那我们要先知道如何判断这个数在某一块中是否是第一次出现。
我们可以搞一个
b
e
f
i
bef_i
befi 记录跟这个数一样的它前面的最后一个数是哪里。
那如果
b
e
f
i
<
l
bef_i<l
befi<l,那它就是在
l
l
l 后面的这个块中第一次出现。
那不难想到要怎么暴力搞,那我们接着看整块怎么搞。
不难想到排序之后二分。
那接着看修改。发现修改之后有三个东西会影响:
- 后面第一个跟它原来颜色的数( b e f bef bef 值是 x x x 的数)
- 后面第一个跟它新的颜色的数( b e f bef bef 将要是 x x x 的数)
- 前面第一个跟它新的颜色的数( x x x 的 b e f bef bef 将要是它)
那修改它们的
b
e
f
bef
bef 不难想到怎么搞,但问题是如何找到。
如果在块内,我们可以直接暴力找。跑出块之后,我们就一个一个块的找,不难想到可以通过二分判断一个数是否在一个块中。(把块中数排序)
然后搞搞就行了,代码比较多,烦,可以看看代码具体实现。
然后由于 luogu 需要卡常,我快读加了之后,还加了一个优化:
当修改
b
e
f
bef
bef 的时候我们不要直接就把那块重新排序,而是打个标记。
然后到时我们二分之前,如果有标记,就再排序,然后清空标记。
(这样可以几次修改才排一次序,让排序次数最小)
代码
#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
int n, m, a[200001], S, s;
int bl[10001], br[10001], x, y;
int lst[1000001], bef[200001];
int aa[200001], beff[200001];
bool nc[200001];//小小的优化,修改之后先不直接更新排序,要用的时候要修改才修改
char op;
int read() {
int re = 0, zf = 1;
char c = getchar();
while (c < '0' || c > '9') {
if (c == '-') zf = -zf;
c = getchar();
}
while (c >= '0' && c <= '9') {
re = (re << 3) + (re << 1) + c - '0';
c = getchar();
}
return re * zf;
}
void Sort(int x) {//求排序
for (int i = bl[x]; i <= br[x]; i++)
aa[i] = a[i], beff[i] = bef[i];
sort(aa + bl[x], aa + br[x] + 1);
sort(beff + bl[x], beff + br[x] + 1);
nc[x] = 0;
}
void premake() {
for (int i = 1; i <= n; i++) {
if (i % S == 1) {
br[s] = i - 1;
bl[++s] = i;
}
}
br[s] = n;
bl[s + 1] = br[s + 1] = n + 1;
for (int i = 1; i <= s; i++) {
nc[i] = 1;
}
}
int query(int block, int x) {//找这一块有多少个数的 bef 值小于 x
if (nc[block]) Sort(block);
int l = bl[block], r = br[block], ans = bl[block] - 1;
while (l <= r) {
int mid = (l + r) >> 1;
if (beff[mid] < x) ans = mid, l = mid + 1;
else r = mid - 1;
}
return ans - bl[block] + 1;
}
bool find(int block, int x) {//找这一块中有没有 x 这个数
if (nc[block]) Sort(block);
int l = bl[block], r = br[block];
while (l <= r) {
int mid = (l + r) >> 1;
if (aa[mid] == x) return 1;
if (aa[mid] < x) l = mid + 1;
else r = mid - 1;
}
return 0;
}
int work(int l, int r) {//查询
int L = (l - 1) / S + 1;
int R = (r - 1) / S + 1;
int re = 0;
if (r - l + 1 <= 2 * S) {
for (int i = l; i <= r; i++)
if (bef[i] < l) re++;
return re;
}
if (l == bl[L]) L--; if (r == br[R]) R++;
for (int i = L + 1; i < R; i++) re += query(i, l);//整块
for (int i = l; i <= br[L]; i++) if (bef[i] < l) re++;//两边
for (int i = bl[R]; i <= r; i++) if (bef[i] < l) re++;
return re;
}
void kill(int x) {//处理后面第一个跟它原来颜色的数(bef 值是 x 的数)
int in = (x - 1) / S + 1;
for (int i = x + 1; i <= br[in]; i++)
if (bef[i] == x) {
bef[i] = bef[x];
nc[in] = 1;
return ;
}
for (int i = in + 1; i <= s; i++) {
if (find(i, a[x])) {
for (int j = bl[i]; j <= br[i]; j++)
if (a[j] == a[x]) {
bef[j] = bef[x];
nc[i] = 1;
return ;
}
}
}
}
void change1(int x, int y) {//处理后面第一个跟它新的颜色的数(bef 将要是 x 的数)
int in = (x - 1) / S + 1;
for (int i = x + 1; i <= br[in]; i++)
if (a[i] == a[x]) {
bef[i] = x;
nc[in] = 1;
return ;
}
for (int i = in + 1; i <= s; i++) {
if (find(i, a[x])) {
for (int j = bl[i]; j <= br[i]; j++)
if (a[j] == a[x]) {
bef[j] = x;
nc[i] = 1;
return ;
}
}
}
}
void change2(int x, int y) {//找到前面第一个跟它新的颜色的数(x 的 bef 将要是它)
int in = (x - 1) / S + 1;
for (int i = x - 1; i >= bl[in]; i--)
if (a[i] == a[x]) {
bef[x] = i;
nc[in] = 1;
return ;
}
for (int i = in - 1; i >= 1; i--)
if (find(i, a[x])) {
for (int j = br[i]; j >= bl[i]; j--)
if (a[j] == a[x]) {
bef[x] = j;
nc[in] = 1;
return ;
}
}
bef[x] = 0;
nc[in] = 1;
}
void change(int x, int y) {
change1(x, y);
change2(x, y);
}
int main() {
n = read(); m = read();
for (int i = 1; i <= n; i++) {
a[i] = read();
bef[i] = lst[a[i]];
lst[a[i]] = i;
}
S = sqrt(n);
premake();
while (m--) {
op = getchar();
while (op != 'Q' && op != 'R') op = getchar();
if (op == 'Q') {
x = read(); y = read();
printf("%d\n", work(x, y));
continue;
}
if (op == 'R') {
x = read(); y = read();
if (a[x] == y) continue;
kill(x);
a[x] = y;
change(x, y);
continue;
}
}
return 0;
}