数颜色 / 维护队列
题目链接:ybt金牌导航6-4-2 / luogu P1903
题目大意
要你维护一个序列,可能会改变一个位置的数,还有询问一个区间内有多少种数。
思路
这道题有多种做法,然后我们考虑用带修莫队来做。
莫队是两个指针第一个按块搞,第二个就按个搞。
那我们带修的话我们就可以加上一个时间指针。
那也是一个道理,我们就也是分成块,然后第一个排序按左边的块排,第二个排序按右边的块排,接着就按着时间排。
然后就每次三个指针移动一下就可以了。
然后至于时间指针的移动,把位置 (x) 的 (a) 换成 (b),就相当于把 (a) 的贡献消除,加上 (b) 的贡献。
然后就差不多了,我们最优会选取 (n^{frac{2}{3}}) 的长度,这样复杂度是 (n^{frac{5}{3}}) 的。、
(搞这个长度直接用 ( ext{pow}) 函数)
代码
#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
struct node {
int t, x, y, num, ans;
}a[140001];
int n, m, x, y, block[140001], t, col[140001];
int fr[140001], to[140001], lst[140001], sz;
int q, tim, pl[140001], num[1000001], ans;
bool in[140001];
char op;
bool cmp(node x, node y) {//莫队的排序
if (block[x.x] != block[y.x]) return block[x.x] < block[y.x];
if (block[x.y] != block[y.y]) return block[x.y] < block[y.y];
return x.t < y.t;
}
void clac(int p) {//更新当前位置的值
if (in[p]) {
num[col[p]]--;
if (!num[col[p]]) ans--;
}
else {
num[col[p]]++;
if (num[col[p]] == 1) ans++;
}
in[p] ^= 1;
}
void change(int p, int t) {//换数
if (in[p]) {
clac(p);
col[p] = t;
clac(p);
}
else col[p] = t;
}
bool cmp1(node x, node y) {
return x.num < y.num;
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d", &lst[i]);
col[i] = lst[i];
}
sz = pow(n, 2.0 / 3);
for (int i = 1; i <= n; i++)
block[i] = (i - 1) / sz + 1;
for (int i = 1; i <= m; i++) {
op = getchar();
while (op != 'Q' && op != 'R') op = getchar();
if (op == 'Q') {
scanf("%d %d", &x, &y);
a[++q] = (node){tim, x, y, i, 0};
}
else {
scanf("%d %d", &x, &y);
pl[++tim] = x;
fr[tim] = lst[x];
to[tim] = y;
lst[x] = y;
}
}
sort(a + 1, a + q + 1, cmp);
t = 0; x = 1; y = 0;
for (int i = 1; i <= q; i++) {
while (t < a[i].t) t++, change(pl[t], to[t]);//三个指针分别移动
while (t > a[i].t) change(pl[t], fr[t]), t--;
while (x < a[i].x) clac(x), x++;
while (x > a[i].x) x--, clac(x);
while (y < a[i].y) y++, clac(y);
while (y > a[i].y) clac(y), y--;
a[i].ans = ans;
}
sort(a + 1, a + q + 1, cmp1);
for (int i = 1; i <= q; i++) printf("%d
", a[i].ans);
return 0;
}