題目傳送門
題目大意:
給定一個長度為 \(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;
}