CF 1436E-Complicated Computations 线段树维护区间MEX

不多说,上代码,代码里有详细的思路解释

#include <cstdio>
#include <cstring>
#include <climits>
#include <vector>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
#define pEnter putchar('\n')
#define sc(n) scanf("%d",&(n))
#define pInt(n) printf("%d\n", (n))
#define plInt(n) printf("%lld\n", (n))
#define mem(a,v) memset((a),v,sizeof(a))
#define scc(n, m) scanf("%d%d",&(n), &(m))
#define sccc(n, m, k) scanf("%d%d%d",&(n), &(m), &(k))
#define pVarInt(v) printf("%s = %d\n",#v,(v))
#define rep(var, s, t) for(int var=(s); var<=(t); var++)
#define drep(var, s, t) for(int var=(s); var>=(t); var--)
void PrintArrInt(int arr[], int s, int e){for(int i=s;i<=e;i++){if(i==s)printf("%d",arr[i]);else printf(" %d",arr[i]);}putchar('\n');}

const int maxn = 100000 + 5;
int N, arr[maxn], minv[maxn*4], mex[maxn], las[maxn], maxv;

/*
线段树动态维护区间MEX
    题意:给出一个数组,对于其任意一个区间求MEX,再求所有MEX构成的数组的MEX
    思路:判断某个数会不会成为某个区间的MEX ->
                如果这个数是这个区间的MEX,那么这个区间肯定不包含这个数  ->
                假设现在要判断数X,那么对于原数组,如果在两个X之间或在一个X和一个左/右端点之间(总之这个区间是不含X的最大区间),
                如果这个区间含有1~X-1的所有数,那么X就是一个MEX  ->
                现在可以利用las数组在遍历原数组时记录上一个相同的值出现位置,然后判断 las[arr[i]] ~ i 之间是否包含 1~arr[i]-1  ->
            !!!!!(这步转化相当重要) 转化为判断 1 ~ arr[i]-1 这些数的在当前的最大索引是否都位于 las[arr[i]] ~ i 之间  ->
                转化为 1 ~ arr[i]-1 这些数的最大索引的最小值是否>las[arr[i]] ->
                以arr[i]的值为线段树的索引,以索引为线段树的值,支持修改和区间最小查询即可
                即:getmin(1, arr[i]-1)>las[arr[i]] 时X为MEX
                (注意不要写成树状数组的实现了,因为要修改值)
*/

void update(int o, int L, int R, int pos, int val) {
    if(L==R) {minv[o]=val; return;}
    int mid=(L+R)>>1;
    if(pos<=mid) update(o*2, L, mid, pos, val);
    else update(o*2+1, mid+1, R, pos, val);
    minv[o]=min(minv[o*2], minv[o*2+1]);
}

int getmin(int o, int L, int R, int lft, int rht) {
    if(lft<=L && rht>=R) return minv[o];
    int mid=(L+R)>>1;
    if(rht<=mid) return getmin(o*2, L, mid, lft, rht);
    else if(lft>mid) return getmin(o*2+1, mid+1, R, lft, rht);
    else return min(getmin(o*2, L, mid, lft, rht), getmin(o*2+1, mid+1, R, lft, rht));
} 

int main() {
    sc(N); rep(i, 1, N) sc(arr[i]), maxv=max(maxv, arr[i]);
    rep(i, 1, N) {
        update(1, 1, maxv, arr[i], i);//每次更新arr[i]位置的索引,使之保持最大
        if(arr[i]==1) continue;//注意特判1
        int res=getmin(1, 1, maxv, 1, arr[i]-1);
        if(res>las[arr[i]]) mex[arr[i]]=1;
        las[arr[i]]=i; mex[1]=1;
    }
    rep(i, 2, maxv+1) {//注意这时las[i] ~ maxv的区间还没有判断
        int res=getmin(1, 1, maxv, 1, i-1);
        if(res>las[i]) mex[i]=1;
    }
    int ans = 1;
    while(mex[ans]) ans++;
    pInt(ans);
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值