【清华集训2014】mex
Description
有一个长度为n的数组{a1,a2,…,an}。m次询问,每次询问一个区间内最小没有出现过的自然数。
Solution
全是查询操作,没有修改,没有强制在线。
考虑用线段树维护,维护什么?
我们把这些点分布在数轴上,那么我们要在这个数轴上找到答案,就要把区间转换到数轴上,也就是要维护每个数的下标。
那么满足维护这个东西的,可以用权值线段树。
这样,我们可以衍生出两种做法:
在线做法:略
离线做法:把所有的询问按照右端点排序,这样我们逐个加入节点(用于确定右端点),在权值线段树中查找比左端点下标小的区间,那么答案肯定在这里面,因为找到的权值下标不可能大于等于 l 。
注意:(这样比我打的动态开节点代码量节省许多)
询问中的 r <script type="math/tex" id="MathJax-Element-7">r</script>可能有很多重复,注意判断即可。
Code
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define fo(i,j,k) for(int i=j;i<=k;i++)
#define N 200001
#define M 1000000000
using namespace std;
struct node{
int l,r,x;
}b[N];
struct dy{
int l,r;
}z[N*10];
int tr[N*10];
int tot=1;
int c[N];
bool cmp(node x,node y)
{
return x.r<y.r;
}
int a[N];
void insert(int v,int l,int r,int x,int p)
{
if(l==r)
{
tr[v]=p;
return;
}
int mid=(l+r)/2;
if(x<=mid)
{
if(!z[v].l) z[v].l=++tot;
insert(z[v].l,l,mid,x,p);
}
else
{
if(!z[v].r) z[v].r=++tot;
insert(z[v].r,mid+1,r,x,p);
}
tr[v]=min(tr[z[v].l],tr[z[v].r]);
}
int find(int v,int l,int r,int x)
{
if(l==r) return l;
int mid=(l+r)/2;
if(tr[z[v].l]<x) return find(z[v].l,l,mid,x);
else return find(z[v].r,mid+1,r,x);
}
int main()
{
int n,m;
cin>>n>>m;
fo(i,1,n) scanf("%d",&a[i]);
fo(i,1,m)
{
scanf("%d %d",&b[i].l,&b[i].r);
b[i].x=i;
}
sort(b+1,b+m+1,cmp);
int p=0;
fo(i,1,n)
{
insert(1,0,M,a[i],i);
fo(j,p+1,m)
if(b[j].r==i) c[b[j].x]=find(1,0,M,b[j].l);
else {p=j-1;break;}
}
fo(i,1,m) printf("%d\n",c[i]);
}