洛谷4137
题目描述
有一个长度为n的数组{a1,a2,…,an}。m次询问,每次询问一个区间内最小没有出现过的自然数。
输入输出格式
输入格式:
第一行n,m。
第二行为n个数。
从第三行开始,每行一个询问l,r。
输出格式:
一行一个数,表示每个询问的答案。
输入输出样例
输入样例#1: 复制
5 5 2 1 0 2 1 3 3 2 3 2 4 1 2 3 5
输出样例#1: 复制
1 2 3 0 3
说明
对于30%的数据:1<=n,m<=1000
对于100%的数据:1<=n,m<=200000,0<=ai<=10^9,1<=l<=r<=n
题解:
今天自己独立做了一道莫队,对其理解更加深刻,说明一下自己对这个算法需要注意的地方和一些新的理解吧;
1.莫队给区间分块后,再一次询问中,l和r在区间内,加起来只会扫一遍块的大小或两个块的大小(可能会跨块移动)
比如有(1,5),(3,3)这两个询问,分块排序后顺序会变变成(3,3)(1,5),为什么不先扫(1,5)就是因为如果先扫(1,5)那么必然导致两个指针会向中间往回扫,扫他们扫过的地方,那就和暴力n^2没区别了,这一正是分块并排序的作用,也是算法的精髓所在。
2.还有一点是分块的大小,正常来说sqrt(n)的大小没问题,但是这题会超时,1000到1500是可以过得,可以稍微往大了放,但是不能太大,那就和没分没区别了
附代码:
#include<stdio.h>
#include<algorithm>
#include<map>
#include<math.h>
#include<string.h>
using namespace std;
struct queu
{
int l,r,id;
}qq[200002];
int a[200002],ans=0;
int anss[200002]={0};
//这里f数组开map<>会RE数组越界,可能是太大了,细细想后最小值最大也是n+1,200000完全ok
int f[300000]={0};
int u=0;
bool cmp(queu a,queu b)
{
return a.l/u==b.l/u ?a.r<b.r:a.l<b.l;
}
void add(int p)
{ f[a[p]]++;
while(f[ans]!=0)
{
ans++;
// printf("%d**\n",ans);
}
return;
}
void dec(int pp)
{
if(a[pp]<ans&&f[a[pp]]==1)
ans=a[pp];
f[a[pp]]--;
return;
}
int main()
{
int n,q,l,r;
while(~scanf("%d%d",&n,&q))
{ans=0;
memset(f,0,sizeof(f));
memset(anss,-1,sizeof(anss));
u=(int)sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
// memset(f,0,sizeof(f));
int ll=1;
int rr=0;
for(int i=0;i<q;i++)
{
scanf("%d%d",&l,&r);
qq[i].l=l;
qq[i].r=r;
qq[i].id=i;
}
sort(qq,qq+q,cmp);
for(int i=0;i<q;i++)
{ while(qq[i].r>rr)add(++rr);
while(qq[i].r<rr)dec(rr--);
while(qq[i].l<ll)add(--ll);
while(qq[i].l>ll)dec(ll++);
anss[qq[i].id]=ans;
}
for(int i=0;i<q;i++)
{
printf("%d\n",anss[i]);
}
}
}