【BZOJ 3585】mex

题目描述

有一个长度为 n n n 的数组 { a 1 , a 2 , … , a n } \{a_1,a_2,\dots ,a_n\} {a1,a2,,an} m m m 次询问,每次询问一个区间内最小没有出现过的自然数。 1 ≤ n , m ≤ 200000 1\le n,m\le 200000 1n,m200000 0 ≤ a i ≤ 1 0 9 0\le a_i\le 10^9 0ai109 1 ≤ l ≤ r ≤ n 1\le l\le r\le n 1lrn

算法分析

莫队算法+分块。

首先可以发现,结果一定不会超过 n − 1 n-1 n1。为什么呢?如果结果超过 n − 1 n-1 n1,那么前面一定被 [ 0 , n − 1 ] [0,n-1] [0,n1] n n n 个数填满了,而 a i a_i ai 的总个数就是 n n n,即最大区间大小不会超过 n n n,也就不存在结果大于 n − 1 n-1 n1 的询问。

按权值分块( 0 0 0 也要算进去),维护每个权值出现的次数同时每块维护其对应值域中自然数出现的个数,询问时只需要找到第一个不满的块在里面暴力寻找第一个出现次数为 0 0 0 的数即可。

时间复杂度为 O ( n n ) O(n\sqrt{n}) O(nn )

好颓废~这道题写完都两天了才更博。

代码实现

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
const int maxn=200005;
#define cmin(x,y) x=std::min(x,y);
#define cmax(x,y) x=std::max(x,y);
struct ask {int id,l,r;} qs[maxn];int sz,bl[maxn],le[maxn],ri[maxn];
inline bool cmp(const ask &x,const ask &y) {return bl[x.l]^bl[y.l]?x.l<y.l:bl[x.l]&1?x.r<y.r:x.r>y.r;}
int a[maxn],cnt[maxn],num[maxn],ans[maxn];
int main() {
	int n,m;scanf("%d%d",&n,&m);
	for(register int i=1;i<=n;++i) {scanf("%d",&a[i]);if(a[i]>n) a[i]=n;}
	for(register int i=0;i<m;++i) {qs[i].id=i;scanf("%d%d",&qs[i].l,&qs[i].r);}
	sz=n/sqrt(m*2/3);for(register int i=0;i<=n;++i) bl[i]=i/sz+1;
	std::sort(qs,qs+m,cmp);memset(le,0x3f,sizeof(le));memset(ri,0xc0,sizeof(ri));
	for(register int i=0;i<=n;++i) {cmin(le[bl[i]],i);cmax(ri[bl[i]],i);}
	for(register int i=0,l=1,r=0;i<m;++i) {
		while(l<qs[i].l) {--cnt[a[l]];if(!cnt[a[l]]) --num[bl[a[l]]];++l;}
		while(l>qs[i].l) {--l;if(!cnt[a[l]]) ++num[bl[a[l]]];++cnt[a[l]];}
		while(r<qs[i].r) {++r;if(!cnt[a[r]]) ++num[bl[a[r]]];++cnt[a[r]];}
		while(r>qs[i].r) {--cnt[a[r]];if(!cnt[a[r]]) --num[bl[a[r]]];--r;}
		int &res=ans[qs[i].id]=0;
		for(register int i=bl[0];i<=bl[n];++i) if(num[i]<ri[i]-le[i]+1) {
			for(int j=le[i];j<=ri[i];++j) if(!cnt[j]) {res=j;break;}
			break;
		}
	}
	for(register int i=0;i<m;++i) printf("%d\n",ans[i]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值