BZOJ 3339: Rmq Problem

本文介绍了一种使用线段树优化区间Mex查询的方法。通过将区间Mex查询转化为针对以1为左端点的区间Mex查询,并利用线段树的特性进行更新,实现了高效查询。文章详细解释了算法思路并提供了实现代码。

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

时空隧道


分析:
我们转化一下,离线,把直接求区间mex转化成先求以1为左端点的区间mex…
我们考虑区间[l,r]的mex和[l+1,r]的mex有什么变化…这些区间的数字少了a[l],如果区间[l,r]的mex大于a[l],那么显然去掉a[l]之后区间[l+1,r]的mex就变成了a[l]…
所以我们按照左端点排序…首先预处理出来所有的[1,r]的mex,然后每次减少一个数字(起点往后加1)就把当前数字的下一次出现位置到当前位置的mex函数全部和当前的左端点取min…
但是如果暴力修改会挂的很惨…还不如写个暴力…
所以说我们考虑mex的性质…
左端点相同的mex是单调不降的…
所以我们可以用线段树来更新mex函数…


代码如下:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio> 
//by NeighThorn
#define inf 5000000
using namespace std;
const int maxn=200000+5;
int n,q,a[maxn],mex[maxn],nxt[maxn],last[maxn],vis[maxn]; 
struct M{
    int l,r,id,ans;
}s[maxn];
struct Tree{
    int l,r,lazy;
}tree[maxn*4];
inline bool cmp1(M a,M b){
    return a.l<b.l;
}
inline bool cmp2(M a,M b){
    return a.id<b.id;
}
inline void build(int l,int r,int tr){
    tree[tr].l=l;tree[tr].r=r;tree[tr].lazy=inf;
    if(l==r){
        tree[tr].lazy=mex[l];
        return;
    }
    int mid=(l+r)>>1;
    build(l,mid,tr<<1),build(mid+1,r,tr<<1|1);
}
inline void change(int l,int r,int val,int tr){
    if(tree[tr].l==l&&tree[tr].r==r){
        tree[tr].lazy=min(tree[tr].lazy,val);
        return;
    }
    if(tree[tr].lazy!=inf&&tree[tr].l!=tree[tr].r){
        tree[tr<<1].lazy=min(tree[tr<<1].lazy,tree[tr].lazy);
        tree[tr<<1|1].lazy=min(tree[tr<<1|1].lazy,tree[tr].lazy);
    }
    int mid=(tree[tr].l+tree[tr].r)>>1;
    if(r<=mid)
        change(l,r,val,tr<<1);
    else if(l>mid)
        change(l,r,val,tr<<1|1);
    else
        change(l,mid,val,tr<<1),change(mid+1,r,val,tr<<1|1);
}
inline int query(int x,int tr){
    if(tree[tr].l==tree[tr].r)
        return min(tree[tr].lazy,mex[x]);
    if(tree[tr].lazy!=inf&&tree[tr].l!=tree[tr].r){
        tree[tr<<1].lazy=min(tree[tr<<1].lazy,tree[tr].lazy);
        tree[tr<<1|1].lazy=min(tree[tr<<1|1].lazy,tree[tr].lazy);
    }
    int mid=(tree[tr].l+tree[tr].r)>>1;
    if(x<=mid)
        return query(x,tr<<1);
    else
        return query(x,tr<<1|1);
}
signed main(void){
    scanf("%d%d",&n,&q);
    memset(last,0,sizeof(last));
    for(int i=1;i<=n;i++)
        nxt[i]=n+1;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        if(last[a[i]])
            nxt[last[a[i]]]=i,last[a[i]]=i;
        else
            last[a[i]]=i;
    }int lala=0;
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++){
        if(a[i]==lala){
            vis[a[i]]=1;
            while(vis[lala])
                lala++;
        }
        else
            vis[a[i]]=1;
        mex[i]=lala;
    }build(1,n,1);
    for(int i=1;i<=q;i++)
        scanf("%d%d",&s[i].l,&s[i].r),s[i].id=i;
    sort(s+1,s+q+1,cmp1);lala=1;
    for(int i=1;i<=n;i++){
        while(s[lala].l==i)
            s[lala].ans=query(s[lala].r,1),lala++;
        change(i,nxt[i]-1,a[i],1);
    }
    sort(s+1,s+q+1,cmp2);
    for(int i=1;i<=q;i++)
        printf("%d\n",s[i].ans);
    return 0;
}

by >_< NeighThorn

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值