【清华集训2014】mex

题目

有一个长度为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]);
    }
}

猜你喜欢

转载自www.cnblogs.com/chen1352/p/9066571.html
今日推荐