[BZOJ3585]-mex-莫队+分块

说在前面

Emmmm闲着没事切一道莫队,半小时码+调
感觉自己码力又增加了


题目

BZOJ3585传送门

题目大意

给出一个长为N的数字串,多次询问区间中没有出现过的自然数的最小值

输入输出格式

输入格式:
第一行两个整数N,M,表示数字串长度和询问个数
接下来N个整数表示数字串
再接下来M行,每行两个整数L,R表示一个询问

输出格式:
对于每个询问,输出一行表示答案


解法

这题解法超多= =
这题把输入离散化之后就是BZOJ3339,可以套用3339的线段树做法(当然要加一点特判)
然后还可以用主席树搞,大概是这样:前往Candy?的blog
树套树没有仔细想,感觉行不通
然后就是莫队暴力了=w=

首先把 询问 按照分块之后排序,然后考虑如何维护mex
统计一个数的出现次数是莫队经典操作,复杂度是 移动复杂度*单次修改复杂度,然后还需要能够接受的求mex的复杂度
查询所有数字中没有出现过的最小数字,可以用线段树来搞。这样单次修改复杂度就是log,总体复杂度就是 NNlogN ,可能过不去…
那么现在需要寻找一种可以修改O(1),查询O(能接受)的算法。比较容易想到按照 值域 分块,求mex时枚举所有的值域块,再进入第一个没有满的块里暴力查找mex(特判如果当前块是满的,并且起始位置原来的数字已经大于整个序列的mex,答案就是整个序列的mex了),这样单次查询就是 N

UPD at 当天下午:才发现自己撒比了…莫队的做法根本不用离散化…因为mex的值最大是N+1,所以在莫队的时候直接判断,如果当前数字比N大的话就直接不操作= =


下面是自带大常数的代码

/**************************************************************
    Problem: 3585
    User: Izumihanako
    Language: C++
    Result: Accepted
    Time:6568 ms
    Memory:9436 kb
****************************************************************/

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

int N , M , val[200005] , uni[200005] , arc[200005] , uninum , ans[200005] , maxmex ;
int Bsiz , Btot , bel[200005] , vcnt[200005] ;
int Bcnt[500] , fullsiz[500] , st[500] , ed[500] ;
struct Unique_Data{
    int num , id ;
    bool operator < ( const Unique_Data &A ) const {
        return num < A.num ;
    }
}Uni[200005] ;

struct Queries{
    int lf , rg , id ;
    bool operator < ( const Queries &A ) const {
        return bel[lf] < bel[A.lf] || 
            ( bel[lf] == bel[A.lf] && rg < A.rg ) ;
    }
}Q[200005] ;

void Unique_(){
    sort( Uni + 1 , Uni + N + 1 ) ;
    for( int i = 1 , las = -1 ; i <= N ; i ++ ){
        if( las != Uni[i].num ){
            uninum ++ ;
            arc[uninum] = Uni[i].num ;
            las = Uni[i].num ;
        }
        uni[ Uni[i].id ] = uninum ;
    }
}

void set_Blo(){
    Bsiz = sqrt( N ) ;
    Btot = ( N / Bsiz ) + ( bool )( N%Bsiz ) ;
    for( int i = 1 ; i <= Btot ; i ++ ){
        st[i] = ed[i-1] + 1 ;
        ed[i] = min( st[i] + Bsiz - 1 , N ) ;
        fullsiz[i] = ed[i] - st[i] + 1 ;
        for( int j = st[i] ; j <= ed[i] ; j ++ )
            bel[j] = i ;
    }
    st[Btot+1] = N + 1 ;
    ed[Btot+1] = 0x3f3f3f3f ;
    fullsiz[Btot+1] = 12345; //必须要设置一个, 不然fullsiz和Bcnt都是0 , 某些数据会GG 
}

void init(){
    Unique_() ;
    set_Blo() ;
    for( int i = 1 ; i <= 200001 ; i ++ )
        if( arc[i] != arc[i-1] + 1 ){
            maxmex = arc[i-1] + 1 ;
            break ;
        }
    sort( Q + 1 , Q + M + 1 ) ;
}

int Query(){
    int tmp ;
    for( int i = 1 ; i <= Btot ; i ++ ){
        if( Bcnt[i] != fullsiz[i] ){
            tmp = i ; break ;
        }
        if( arc[ st[i] ] >= maxmex ) return maxmex ;
    }
    for( int i = st[tmp] ; i <= ed[tmp] ; i ++ ){
        if( i >= maxmex ) return maxmex ;
        if( !vcnt[i] ) return arc[i] ;
    }
}

void solve(){
    int L = 1 , R = 0 ;
    for( int i = 1 ; i <= M ; i ++ ){
        int lf = Q[i].lf , rg = Q[i].rg ;
        while( R < rg ){
            R ++ ;
            vcnt[ uni[R] ] ++ ;
            if( vcnt[ uni[R] ] == 1 ) Bcnt[ bel[uni[R]] ] ++ ;
        }
        while( R > rg ){
            vcnt[ uni[R] ] -- ;
            if( vcnt[ uni[R] ] == 0 ) Bcnt[ bel[uni[R]] ] -- ;
            R -- ;
        }
        while( L > lf ){
            L -- ;
            vcnt[ uni[L] ] ++ ;
            if( vcnt[ uni[L] ] == 1 ) Bcnt[ bel[uni[L]] ] ++ ;
        }
        while( L < lf ){
            vcnt[ uni[L] ] -- ;
            if( vcnt[ uni[L] ] == 0 ) Bcnt[ bel[uni[L]] ] -- ;
            L ++ ;
        }
        ans[ Q[i].id ] = Query() ;
    }
    for( int i = 1 ; i <= M ; i ++ )
        printf( "%d\n" , ans[i] - 1 ) ;
}

int main(){
    scanf( "%d%d" , &N , &M ) ;
    for( int i = 1 ; i <= N ; i ++ ){
        scanf( "%d" , &val[i] ) ; val[i] ++ ;
        Uni[i].num = val[i] , Uni[i].id = i ;
    }
    for( int i = 1 ; i <= M ; i ++ ){
        scanf( "%d%d" , &Q[i].lf , &Q[i].rg ) ;
        Q[i].id = i ;
    }
    init() ;
    solve() ;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值