有一个长度为n的数组{a1,a2,...,an}。m次询问,每次询问一个区间内最小没有出现过的自然数。

分析

显然,当\(a_i>n\)时,对答案没有影响,所以全部视为n+1。
有两种方法,主席树和权值线段树。
主席树裸题,就讲权值线段树。
首先将询问按r排序,将1~r的\(a_i\)全部加入权值线段树,记录它最晚出现的位置,对于每个区间记录这个区间中每个数最晚出现的位置的最小值mn。
查询一个l,当\(该区间左儿子的mn<l\),显然左儿子中有个\(a_i\)不在区间[l,r]中,就查询左儿子,否则查询右儿子。

#include <cmath>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <queue>
const int maxlongint=2147483647;
const int mo=1000000007;
const int N=200005;
using namespace std;
int a[N],pos[N*5],mn[N*5],n,m,ans[N],lim;
struct ddx
{
	int x,y,z;
}re[N];
bool cmp(ddx x,ddx y)
{
	return x.y<y.y || x.y==y.y && x.x<y.x;
}
void put(int l,int r,int v,int aim,int j)
{
	if(l==r)
	{
		pos[v]=j;
		mn[v]=j;
		return;
	}
	int mid=(l+r)/2;
	if(aim<=mid)
		put(l,mid,v*2,aim,j);
	else
		put(mid+1,r,v*2+1,aim,j);
	mn[v]=min(mn[v*2],mn[v*2+1]);
}
int find(int l,int r,int v,int aim)
{
	if(l==r)
	{
		return l;
	}
	int mid=(l+r)/2;
	if(mn[v*2]<aim)
		return find(l,mid,v*2,aim);
	else
		return find(mid+1,r,v*2+1,aim);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		if(a[i]>n) a[i]=n+1;
	}
	for(int i=1;i<=m;i++) 
	{
		scanf("%d%d",&re[i].x,&re[i].y);
		re[i].z=i;
	}
	sort(re+1,re+1+m,cmp);
	lim=1;
	for(int i=1;i<=m;i++)
	{
		while(re[i].y>=lim)
		{
			put(0,n+1,1,a[lim],lim);
			lim++;
		}
		ans[re[i].z]=find(0,n+1,1,re[i].x);
	}
	for(int i=1;i<=m;i++)
	{
		printf("%d\n",ans[i]);
	}
}

相关文章: