题解 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;
}
posted @ 2020-03-15 10:05  Dzhao_qwq  阅读(209)  评论(0)    收藏  举报