题目地址:
https://www.acwing.com/problem/content/description/2523/
墨墨购买了一套
N
N
N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。墨墨会像你发布如下指令:
Q L R
代表询问你从第
L
L
L支画笔到第
R
R
R支画笔中共有几种不同颜色的画笔。
R P Col
把第
P
P
P支画笔替换为颜色Col。
为了满足墨墨的要求,你知道你需要干什么了吗?
输入格式:
第
1
1
1行两个整数
N
,
M
N,M
N,M,分别代表初始画笔的数量以及墨墨会做的事情的个数。
第
2
2
2行
N
N
N个整数,分别代表初始画笔排中第
i
i
i支画笔的颜色。
第
3
3
3行到第
2
+
M
2+M
2+M行,每行分别代表墨墨会做的一件事情,格式见题干部分。
输出格式:
对于每一个Query的询问,你需要在对应的行中给出一个数字,代表第
L
L
L支画笔到第
R
R
R支画笔中共有几种不同颜色的画笔。
数据范围:
1
≤
N
,
M
≤
10000
1≤N,M≤10000
1≤N,M≤10000
修改操作不多于
1000
1000
1000次
所有的输入数据中出现的所有整数均大于等于
1
1
1且不超过
1
0
6
10^6
106。
带修改莫队。莫队基本思想参考https://blog.csdn.net/qq_46105170/article/details/125827776。本题里还有修改操作,所以我们挪动指针的时候,除了挪动区间端点的两个指针以外,还需要挪动时间指针。首先将每个询问和修改都存起来,存修改的时候,数组下标即为此次修改的时间戳;这样对于每个询问操作,除了记录该询问的编号、左右端点之外,还要把时间戳记录下来。
接下来类似莫队的操作,对询问排序,先按左端点所在分块的编号递增排,左端点所在块相同则按右端点所在分块的编号递增排,如果还相同,为了时间指针移动次数尽量少,按时间递增排。
接下来处理每个询问,开三个指针 i , j , t i, j, t i,j,t分别代表左右端点的指针和时间指针。一开始 t = 0 t=0 t=0。先按基础莫队的办法,将 i i i和 j j j移动到左右端点,同时更新计数。端点指针移动到位之后,开始移动时间指针。如果 t t t小于询问时间,那么我们顺着时间增长的方向修改数组,并且如果修改的位置是区间内的,还需要更新答案;如果 t t t大于询问时间,那么我们顺着时间逆流的方向修改数组,同样的,如果修改的位置是区间内的,还需要更新答案。为了方便操作,在修改的时候,我们直接将被修改的数和修改操作里记录的数做交换,这样时间顺流的时候如果修改是 x → y x\to y x→y,那么时间逆流的时候修改就是 y → x y\to x y→x,这样恰好满足需求。
考虑指针移动的总次数。设块的大小为 a a a,则 j j j指针移动次数为 O ( a m + 2 n ) O(am+2n) O(am+2n), t t t指针移动次数为 O ( n 2 2 a 2 T ) O(\frac{n^2}{2a^2}T) O(2a2n2T), T T T为时间戳最大值,即修改总次数(固定 i i i和 j j j所在块之后, t t t最多移动 T T T次,而所在块的方案数大概是 n 2 2 a 2 \frac{n^2}{2a^2} 2a2n2)。而对于 i i i指针, j j j块内移动的时候,其最多移动 O ( n ) O(n) O(n),总共 O ( n 2 a ) O(\frac{n^2}{a}) O(an2),当 j j j跨块的时候,总次数 O ( n a m ) O(\frac{n}{a}m) O(anm),所以总共是 O ( a m + 2 n + n 2 2 a 2 T + n 2 a + n a m ) O(am+2n+\frac{n^2}{2a^2}T+\frac{n^2}{a}+\frac{n}{a}m) O(am+2n+2a2n2T+an2+anm)。如果分块每块大小 n \sqrt n n的话,每次询问平均需要 O ( n + T ) O(\sqrt n+T) O(n+T)(注意本题中 n ∼ m n\sim m n∼m)。
代码如下:
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 1e4 + 10, S = 1e6 + 10;
int n, m, len, mc, mq;
int w[N], cnt[S], res[N], ans;
struct Query {
int id, l, r, t;
} q[N];
// c[t]代表t这个时间戳,做了将w[p]变成c的操作。t从1开始
struct Modify {
int p, c;
} c[N];
void add(int x) {
if (!cnt[x]) ans++;
cnt[x]++;
}
void del(int x) {
cnt[x]--;
if (!cnt[x]) ans--;
}
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 = sqrt(n);
sort(q + 1, q + 1 + mq, [&](Query& a, Query& b) {
int al = a.l / len, bl = b.l / len;
if (al != bl) return al < bl;
int ar = a.r / len, br = b.r / len;
// 单调优化(玄学优化)
if (ar != br) if (al & 1) return ar < br; else return ar > br;
return a.t < b.t;
});
for (int k = 1, i = 0, j = 1, t = 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]);
while (i > r) del(w[i--]);
while (j < l) del(w[j++]);
while (j > l) add(w[--j]);
while (t < tm) {
t++;
if (l <= c[t].p && c[t].p <= r) {
del(w[c[t].p]);
add(c[t].c);
}
swap(w[c[t].p], c[t].c);
}
while (t > tm) {
if (l <= c[t].p && c[t].p <= r) {
del(w[c[t].p]);
add(c[t].c);
}
swap(w[c[t].p], c[t].c);
t--;
}
res[id] = ans;
}
for (int i = 1; i <= mq; i++) printf("%d\n", res[i]);
}
预处理时间复杂度 O ( m q log m q ) O(m_q\log m_q) O(mqlogmq), m q m_q mq为询问次数,每次询问时间 O ( n + T ) O(\sqrt n+T) O(n+T), T T T为最大时间戳,空间 O ( m + S ) O(m+S) O(m+S), S S S为不同颜色数量。