SP30906 解题报告
题目传送门
题目大意:
给定一个长度为 \(n\) 的序列,\(q\) 次询问区间 \([l, r]\) 内只出现过一次的数有多少个。
思路:
很明显带修莫队可以做。
复习一下,带修莫队就是在普通莫队的基础上加上了时间轴,把操作分为询问操作和修改操作两种分别存下来。
因为修改是有顺序的,每次修改只会会对它之后的查询操作有变动,而对它之前的查询不影响。
令当前时间为 \(t\),若当前时间小于所处理的查询操作的时间,就将时间往后推进,增添几个修改,反之时间回溯,减少几个修改,直到当前的所有变量与所查询区间重合。
通俗地讲,就是再弄一指针,在修改操作上跳来跳去,如果当前修改多了就改回来,改少了就改过去,直到次数恰当为止。
排序也需要加上时间这一关键字。
就题而言,因为只要求出现一次的数字个数,所以在加数时若没出现过则答案 \(+1\),否则若刚好出现过一次就要 \(-1\)。删数操作大同小异。
\(\texttt{Code:}\)
#include <cmath>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 200010;
int n, m;
int a[N];
int len;
inline int get(int x) {return x / len;}
struct Q{
int id, l, r, t;
bool operator <(const Q &o) const{
if(get(l) != get(o.l)) return l < o.l;
if(get(r) != get(o.r)) return r < o.r;
return t < o.t;
}
}q[N];
int ttq;
struct M{
int x, y;
}qc[N];
int ttc;
int cnt[N];
int ans[N];
inline void add(int x, int &res) {
if(!cnt[x]) res++;
else if(cnt[x] == 1) res--;
cnt[x]++;
}
inline void del(int x, int &res) {
cnt[x]--;
if(!cnt[x]) res--;
else if(cnt[x] == 1) res++;
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
int op, x, y;
while(m--) {
scanf("%d%d%d", &op, &x, &y);
if(op == 2) q[++ttq] = {ttq, x + 1, y + 1, ttc};
else qc[++ttc] = {x + 1, y};
}
len = pow(n, 2.0 / 3); //从队内大佬处学来的玄学块长
sort(q + 1, q + ttq + 1);
int id, l, r, tg;
for(int i = 0, j = 1, t = 0, k = 1, res = 0; k <= ttq; k++) {
id = q[k].id, l = q[k].l, r = q[k].r, tg = q[k].t;
while(i < r) add(a[++i], res);
while(i > r) del(a[i--], res);
while(j < l) del(a[j++], res);
while(j > l) add(a[--j], res);
while(t < tg) {
++t;
if(qc[t].x >= j && qc[t].x <= i) {
del(a[qc[t].x], res);
add(qc[t].y, res);
}
swap(a[qc[t].x], qc[t].y);
}
while(t > tg) {
if(qc[t].x >= j && qc[t].x <= i) {
del(a[qc[t].x], res);
add(qc[t].y, res);
}
swap(a[qc[t].x], qc[t].y);
--t;
}
ans[id] = res;
}
for(int i = 1; i <= ttq; i++)
printf("%d\n", ans[i]);
return 0;
}