【清华集训2014】mex

本文介绍了一种利用权值线段树解决区间查询问题的方法,即在一个数组中找到某个区间内最小的未出现过的自然数。通过离线处理所有查询并排序,逐个加入节点并更新线段树,实现高效查询。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

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

Solution

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

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

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

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

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

在线做法:略

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

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

询问中的r可能有很多重复,注意判断即可。

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]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值