题解 P4137 【Rmq Problem / mex】
先分析下本题:
给你一个序列,有m个询问,每次询问问一段区间中的 mex(即最小未出现的自然数)。
看到只有询问,而且还有一个莫名玄学的询问操作,我们肯定会往莫队上想,那么,我们现在就需要知道莫队怎么敲。
莫队模板我就不多说了,我们需要知道的是怎么对当前的询问给出答案。
法1. 暴力
这个我就不说了,复杂度$O(n)$,算上莫队复杂度,总复杂度$O(n^2)$,自己咕咕咕去吧。
法2. 分块
因为我们是求区间中最小未出现的数,而我们可以用 cnt 数组求出每个数出现的次数,我们把数分成$\sqrt n$块,然后对每一块进行统计就行了。
当一个块中的数的种数等于这块的大小,那么答案肯定不在这一块里,否则就再在这一块中找答案,这样,就将一次询问的复杂度降成了$O(\sqrt n)$。
因为莫队复杂度也为$O(\sqrt n)$,所以不影响总复杂度。
总复杂度为$O(n \sqrt n)$
(ps.因为总共就 $n$ 个数,可以由抽屉原理推得答案不超过 $n+1$ ,于是我们就可以将大于 $n$ 的数都变成 $n+1$ 就行了)
法3.玄学(光速逃
$View\ Code$
#include<bits/stdc++.h>
using namespace std;
const int N=200005;
const int M=455;
inline int max(int x,int y) {return x>y?x:y;}
inline int min(int x,int y) {return x<y?x:y;}
inline int read() //快读
{
int x=0,f=1;
char c=getchar();
while(c<'0' || c>'9') {if(c=='-') f=-1;c=getchar();}
while(c>='0' && c<='9') x=x*10+c-'0',c=getchar();
return x*f;
}
int tot[M],cnt[N],bl,blo,l,r,ans[N],a[N];
struct query
{
int l,r,id;
bool operator < (const query &b) const //按照所在块排序
{
if(l/bl!=b.l/bl) return l<b.l;
return r<b.r;
}
}q[N];
inline void add(int pos) //添加
{
if(!cnt[a[pos]]) tot[a[pos]/blo]++;
cnt[a[pos]]++;
}
inline void del(int pos) //删除
{
cnt[a[pos]]--;
if(!cnt[a[pos]]) tot[a[pos]/blo]--;
}
inline int ask() //分块查询
{
for(int i=0;i<=blo;i++)
if(tot[i]!=blo)
for(int j=0;j<blo;j++) if(!cnt[i*blo+j]) return i*blo+j;
}
int main()
{
int n=read(),m=read();
bl=sqrt(n);
for(int i=1;i<=n;i++) a[i]=min(read(),n+1);
blo=sqrt(n);
for(int i=1;i<=m;i++)
{
q[i].id=i;
q[i].l=read();
q[i].r=read();
}
sort(q+1,q+m+1);
l=1,r=0;
for(int i=1;i<=m;i++) //莫队操作
{
while(l>q[i].l) add(--l);
while(r<q[i].r) add(++r);
while(l<q[i].l) del(l++);
while(r>q[i].r) del(r--);
ans[q[i].id]=ask();
}
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}

浙公网安备 33010602011771号