题目描述
有一个长度为 nnn 的数组 {a1,a2,…,an}\{a_1,a_2,\dots ,a_n\}{a1,a2,…,an}。mmm 次询问,每次询问一个区间内最小没有出现过的自然数。1≤n,m≤2000001\le n,m\le 2000001≤n,m≤200000,0≤ai≤1090\le a_i\le 10^90≤ai≤109,1≤l≤r≤n1\le l\le r\le n1≤l≤r≤n。
算法分析
莫队算法+分块。
首先可以发现,结果一定不会超过 n−1n-1n−1。为什么呢?如果结果超过 n−1n-1n−1,那么前面一定被 [0,n−1][0,n-1][0,n−1] 这 nnn 个数填满了,而 aia_iai 的总个数就是 nnn,即最大区间大小不会超过 nnn,也就不存在结果大于 n−1n-1n−1 的询问。
按权值分块(000 也要算进去),维护每个权值出现的次数同时每块维护其对应值域中自然数出现的个数,询问时只需要找到第一个不满的块在里面暴力寻找第一个出现次数为 000 的数即可。
时间复杂度为 O(nn)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;
}