【清华集训2014】mex

Description

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

Solution

全是查询操作,没有修改,没有强制在线。

考虑用线段树维护,维护什么?

我们把这些点分布在数轴上,那么我们要在这个数轴上找到答案,就要把区间转换到数轴上,也就是要维护每个数的下标。

那么满足维护这个东西的,可以用权值线段树。

这样,我们可以衍生出两种做法:

在线做法:略

离线做法:把所有的询问按照右端点排序,这样我们逐个加入节点(用于确定右端点),在权值线段树中查找比左端点下标小的区间,那么答案肯定在这里面,因为找到的权值下标不可能大于等于 l

注意:ai很大,但我们发现答案最大为 n+1 ,于是所有的大于 n ai可直接修改为 n+1 ,线段树就可以用堆式存储。(这样比我打的动态开节点代码量节省许多)

询问中的 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]);
}
posted @ 2016-07-06 15:16  sadstone  阅读(48)  评论(0编辑  收藏  举报