题目传送门


这道题不算特别难,基本算得上是带修莫队的板子题,首先应该想到的是统计每个数字出现的次数,不过这道题比较有趣的一点是,要求查询的是每个数字出现次数的 Mex,所以还应该统计一个出现次数的出现次数,具体来说,我们用一个 cntcnt 数组来统计数字出现次数,然后再用一个 cnt2cnt2 数组来统计 cntcnt 数组内每个数字的出现次数,在更新 cntcnt 的时候顺便也更新一下 cnt2cnt2 就可以了。

但有一点不太好想的是如何求这个出现次数的 Mex,一开始,我想的是可以跟着 cnt2cnt2 数组一起更新,但这样会有很多问题,因为我们无法判断如果 cnt2anscnt2_{ans} 不为 00 了,那么答案应该是多少,所以我采用了一个很玄学的方法,用一个 bitset 来统计哪些出现次数是没有出现过的,最后找一个最小的就可以了,这样我们只需要在更新 cnt2cnt2 的时候判断一下这个被更新的值是否从 0011 或是从 1100,然后相对应地把 bitset 也更新一下就好了。而在找的时候,可以用 bitset 的一个成员函数_Find_first,它的作用是寻找这个 bitset 中第一个 11,并且返回它的位置,也就是所有数字出现次数的 Mex。

对了,这道题由于值域为 [1,109][1,10^9],所以需要离散化,在离散化时要注意,不仅对于 aa 数组要离散化,对于修改操作中要求改成的那些数也要进行离散化,而且应该是一起进行,否则有可能会 RE,同时,由于这两部分是一起离散化的,所以最终离散后的值域应该是 [1,2×n][1,2\times n],记得要把数组大小开够,否则也会 RE。

参考代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#include<bits/stdc++.h>
#define N 200010//数组大小开够!
using namespace std;
int n,m,p,len,k,k1,lastc,l=1,r,t,ans[N],cnt[N],cnt1[N],a[N];
struct Query
{
int x;
int y;
int z;
int id;
bool operator < (Query const &a) const
{
return ((x/len)^(a.x/len)) ? x/len<a.x/len : ((y/len)^(a.y/len)) ? y/len<a.y/len : z<a.z;
//带修莫队不像普通莫队,在排序时应当以左端点所在的块为第一关键字,右端点所在的块为第二关键字,时间为第三关键字
}
};
struct Change
{
int p;
int v;
};
Query q[N];
Change c[N];
vector<int> rec;//用来离散的数组,为了方便用了 vector
bitset<N> num;//统计答案的 bitset
void add(int i)
{
cnt1[cnt[a[i]]]--;
if(!cnt1[cnt[a[i]]])//先把当前数字出现次数的出现次数减一
num[cnt[a[i]]]=1;
cnt[a[i]]++;
if(!cnt1[cnt[a[i]]])//再把当前数字出现次数的出现次数加一
num[cnt[a[i]]]=0;
cnt1[cnt[a[i]]]++;
}
void del(int i)
{
cnt1[cnt[a[i]]]--;
if(!cnt1[cnt[a[i]]])//操作基本同上
num[cnt[a[i]]]=1;
cnt[a[i]]--;
if(!cnt1[cnt[a[i]]])
num[cnt[a[i]]]=0;
cnt1[cnt[a[i]]]++;
}
int main()
{
scanf("%d%d",&n,&m);
len=(int)pow(n,2.0/3.0);//这也是要注意的一点,带修莫队的块长应该为n^(2/3)
rec.push_back(0);//防止离散后的值为 0(其实不加也能 AC,但我觉得要更严谨一点)
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
rec.push_back(a[i]);
}
for(int i=1;i<=m;i++)
{
int op,l,r;
scanf("%d%d%d",&op,&l,&r);
if(op==1)
q[++k]=Query{l,r,lastc,k};
else
{
k1++;
lastc=k1;
rec.push_back(r);
c[k1]=Change{l,r};
}
}
sort(rec.begin(),rec.end());
rec.erase(unique(rec.begin(),rec.end()),rec.end());
for(int i=1;i<=n;i++)
a[i]=lower_bound(rec.begin(),rec.end(),a[i])-rec.begin();
for(int i=1;i<=k1;i++)
c[i].v=lower_bound(rec.begin(),rec.end(),c[i].v)-rec.begin();
sort(q+1,q+1+k);
cnt1[0]=0x3f3f3f3f;//由于答案一定不为 0,所以可以把 cnt1[0] 设为无穷大
num.set();//bitset 成员函数之一,把所有值设为 1
num[0]=0;
for(int i=1;i<=k;i++)
{
while(l>q[i].x)
add(--l);
while(r<q[i].y)
add(++r);
while(l<q[i].x)
del(l++);
while(r>q[i].y)
del(r--);
while(t<q[i].z)
{
t++;
if(l<=c[t].p&&r>=c[t].p)
{
del(c[t].p);
swap(a[c[t].p],c[t].v);
add(c[t].p);
}
else
swap(a[c[t].p],c[t].v);
}
while(t>q[i].z)
{
if(l<=c[t].p&&r>=c[t].p)
{
del(c[t].p);
swap(a[c[t].p],c[t].v);
add(c[t].p);
}
else
swap(a[c[t].p],c[t].v);
t--;
}
ans[q[i].id]=num._Find_first();//上面讲过了,获取出现次数的 Mex
}
for(int i=1;i<=k;i++)
printf("%d\n",ans[i]);
return 0;
}