zoukankan      html  css  js  c++  java
  • 牛客练习赛68 A-牛牛的Mex

    牛客练习赛68 A-牛牛的Mex

    传送门

    题意

    给一个0~n-1的排列,q个询问,询问区间的Mex。

    Mex定义为最小未出现的自然数。

    题解

    我们队三个人看到这题第一反应上莫队,jhlp哈哈哈哈

    然后掏出莫队板子火速A了

    我是赛后再来回顾的,发现由于这题数组比较特殊是有更符合这道题背景的做法的。

    莫队

    首先考虑莫队做法

    莫队的话首先要对询问区间排个序,然后主要就是考虑Add和Del函数怎么写。

    用一个cnt数组记录在当前区间某个数出现过的次数,tmpans代表当前区间的Mex,初始置为0

    ①Add,区间扩展的时候,扩展的这个位置上的这个数的cnt++。考虑这一位的cnt++会给答案造成什么样的影响呢?

    影响就是

    (1)如果当前这个位置就是原来的Mex的话,那原来的这个Mex就不成立了,我还得重新寻找新的Mex,由于之前的Mex是之前区间最小的没有出现过的自然数,那么小于Mex的值不可能成为新的Mex,所以一个while循环往大于Mex的方向寻找,直到碰到一个cnt为0的数停止找到当前的Mex。

    (2)如果当前位置不是原来的Mex,

    ​ 1.是比Mex大的数显然不会影响Mex的值。

    ​ 2.如果是比Mex小的值也不会影响Mex,因为Mex之所以是Mex,就是因为比他小的都已经出现过了,所以比他小的再出现也不会影响什么。

    ②Del,区间收缩的时候,收缩位置的值的cnt--,会对答案造成什么样的影响呢?

    影响就是,

    1.如果当前位置的cnt--之后变为0了,就有可能成为新的Mex。设当前位置为x,当前位置的值为a[x]

    (1)a[x]>Mex,那么Mex不变;

    (2)a[x]<Mex,那么a[x]代替Mex成为新的Mex;

    (3)不可能,因为Mex不可能在当前区间存在。

    2.如果当前位置的cnt--之后没有变为0,那么显然对答案不会产生影响。

    以上,Add和Del就讨论完了。下面是学习莫队以及写这道题碰到的一些坑点

    坑点

    1.四个While循环一定要先Add再Del,防止cnt数组变为负数,你可能会觉得变为负数怎么了,反正最后会变回来,NoNoNo,在此感谢ztc大佬替我想的WA样例。

    5 1
    2 0 1 3 4
    3 5

    你会发现如果先Del你的答案可能会变成2

    因为cnt变为负数,使得这个cnt有可能在Add的过程中变到0,而这又是我们在上面的Add中没有讨论到的情况,因此需要先Add再Del尽可能保证答案的正确性,以免意外的发生。

    2.这个坑点虽然我没有遇到过,但是ztc大佬提了一嘴,我就写下来为以后的莫队不停地T或者WA的时候提供一种错误可能性的参考吧,就是用莫队的奇偶优化的时候有可能会产生一些越界问题什么的,T掉,bshd啦

    3.WA的时候可以尝试换个query的cmp函数,可能就过了,也是作为一个尝试的方向吧

    代码

    /****************************
    * Author : W.A.R            *
    * Date : 2020-08-30-01:21   *
    ****************************/
    /*
    莫队求区间Mex 
    */
    #include<stdio.h>
    #include<string.h>
    #include<math.h>
    #include<algorithm>
    #include<queue>
    #include<map>
    #include<stack>
    #include<string>
    using namespace std;
    typedef long long ll;
    const int maxn=3e6+10;
    const ll mod=1e9+7;
    namespace Fast_IO{
        const int MAXL((1 << 18) + 1);int iof, iotp;
        char ioif[MAXL], *ioiS, *ioiT, ioof[MAXL],*iooS=ioof,*iooT=ioof+MAXL-1,ioc,iost[55];
        char Getchar(){
            if (ioiS == ioiT){
                ioiS=ioif;ioiT=ioiS+fread(ioif,1,MAXL,stdin);return (ioiS == ioiT ? EOF : *ioiS++);
            }else return (*ioiS++);
        }
        void Write(){fwrite(ioof,1,iooS-ioof,stdout);iooS=ioof;}
        void Putchar(char x){*iooS++ = x;if (iooS == iooT)Write();}
        inline int read(){
            int x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
    		if(ioc==EOF)exit(0);
            for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
        }
        inline long long read_ll(){
            long long x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
    		if(ioc==EOF)exit(0);
            for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
        }
        template <class Int>void Print(Int x, char ch = ''){
            if(!x)Putchar('0');if(x<0)Putchar('-'),x=-x;while(x)iost[++iotp]=x%10+'0',x/=10;
            while(iotp)Putchar(iost[iotp--]);if (ch)Putchar(ch);
        }
        void Getstr(char *s, int &l){
            for(ioc=Getchar();ioc==' '||ioc=='
    '||ioc=='	';)ioc=Getchar();
    		if(ioc==EOF)exit(0);
            for(l=0;!(ioc==' '||ioc=='
    '||ioc=='	'||ioc==EOF);ioc=Getchar())s[l++]=ioc;s[l] = 0;
        }
        void Putstr(const char *s){for(int i=0,n=strlen(s);i<n;++i)Putchar(s[i]);}
    } // namespace Fast_IO 
    using namespace Fast_IO;
    int block[maxn],cnt[maxn],a[maxn];
    struct Query{int l,r,id;}q[maxn];
    inline bool cmp(Query a,Query b){return (block[a.l])^(block[b.l])?block[a.l]<block[b.l]:(((block[a.l])&1)?a.r<b.r:a.r>b.r);}
    int tmpans=0,k,ans[maxn];
    inline void Add(int x){cnt[a[x]]++;while(cnt[tmpans])tmpans++;}
    inline void Del(int x){cnt[a[x]]--;if(!cnt[a[x]])tmpans=min(tmpans,a[x]);}
    ll Calc(){return tmpans;}
    int main(){
        int n=read(),Q=read(),l=1,r=0;
        int blocks=sqrt(n);for(int i=1;i<=n;i++)block[i]=(i-1)/blocks;
        for(int i=1;i<=n;i++)a[i]=read();
        for(int i=1;i<=Q;i++)q[i].l=read(),q[i].r=read(),q[i].id=i;
        sort(q+1,q+1+Q,cmp);
        for(int i=1;i<=Q;i++){
            while(l>q[i].l)Add(--l);
            while(r<q[i].r)Add(++r);
    		while(l<q[i].l)Del(l++);
            while(r>q[i].r)Del(r--);
            ans[q[i].id]=Calc();
        }
        for(int i=1;i<=Q;i++)printf("%lld
    ",ans[i]);return 0;
    }
    

    此题正解

    此题正解是维护数组的前缀最小值pre和后缀最小值suf,对于每一个询问[ l , r ],(min(pre[l-1],suf[r+1]))就是答案。

    为什么呢?

    因为题目中规定了给出的是一个([0,n-1])的排列,0~n-1的数全都会出现,且每个数只会出现一次。

    因此对于询问的区间的Mex,首先区间里的数不可能是答案,答案只可能在除询问区间外的部分产生。

    询问区间外的数首先满足了区间未出现过这个条件,那如何满足最小这个条件呢?就取min就可以啦,所以询问区间左边的部分用pre前缀最小值数组维护,询问区间右边的部分用suf后缀最小值维护答案,最后二者取个min就是答案啦。

    注意点

    如果询问的是整个区间的话那么答案一定是n,所以需要在pre[0]和suf[n+1]的位置赋值为n

    这样答案就很完美啦

    代码

    /****************************
    * Author : W.A.R            *
    * Date : 2020-08-30-01:21   *
    ****************************/
    /*
    */
    #include<stdio.h>
    #include<string.h>
    #include<math.h>
    #include<algorithm>
    #include<queue>
    #include<map>
    #include<stack>
    #include<string>
    using namespace std;
    typedef long long ll;
    const int maxn=1e5+50;
    const ll mod=1e9+7;
    namespace Fast_IO{
        const int MAXL((1 << 18) + 1);int iof, iotp;
        char ioif[MAXL], *ioiS, *ioiT, ioof[MAXL],*iooS=ioof,*iooT=ioof+MAXL-1,ioc,iost[55];
        char Getchar(){
            if (ioiS == ioiT){
                ioiS=ioif;ioiT=ioiS+fread(ioif,1,MAXL,stdin);return (ioiS == ioiT ? EOF : *ioiS++);
            }else return (*ioiS++);
        }
        void Write(){fwrite(ioof,1,iooS-ioof,stdout);iooS=ioof;}
        void Putchar(char x){*iooS++ = x;if (iooS == iooT)Write();}
        inline int read(){
            int x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
    		if(ioc==EOF)exit(0);
            for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
        }
        inline long long read_ll(){
            long long x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
    		if(ioc==EOF)exit(0);
            for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
        }
        template <class Int>void Print(Int x, char ch = ''){
            if(!x)Putchar('0');if(x<0)Putchar('-'),x=-x;while(x)iost[++iotp]=x%10+'0',x/=10;
            while(iotp)Putchar(iost[iotp--]);if (ch)Putchar(ch);
        }
        void Getstr(char *s, int &l){
            for(ioc=Getchar();ioc==' '||ioc=='
    '||ioc=='	';)ioc=Getchar();
    		if(ioc==EOF)exit(0);
            for(l=0;!(ioc==' '||ioc=='
    '||ioc=='	'||ioc==EOF);ioc=Getchar())s[l++]=ioc;s[l] = 0;
        }
        void Putstr(const char *s){for(int i=0,n=strlen(s);i<n;++i)Putchar(s[i]);}
    } // namespace Fast_IO 
    using namespace Fast_IO;
    int pre[maxn],suf[maxn],a[maxn];
    int main(){
    	int n=read(),q=read();pre[0]=n;suf[n+1]=n;
    	for(int i=1;i<=n;i++)a[i]=read();
    	for(int i=1;i<=n;i++)pre[i]=min(pre[i-1],a[i]);
    	for(int i=n;i>=1;i--)suf[i]=min(suf[i+1],a[i]);
    	while(q--){
    		int l=read(),r=read();
    		printf("%d
    ",min(pre[l-1],suf[r+1]));
    	}
    }
    

    乱7788糟

    本来就想着回顾一下莫队

    结果就感觉增加了好多奇怪的知识,果然要保持一颗好奇的心哦

    我发现自己莫队还是不太会写啦,就很离谱,主要是碰到新的题目就不会该Add和Del。还有就是大佬告诉我正常的莫队求Mex应该要再套个分块或者线段树,否则这样纯暴力是可以被卡掉的,有空再学学怎么写更新上来吧(咕咕咕~

  • 相关阅读:
    Python中的魔法方法【技能突破】
    Python简明教程:面向对象【新手必学】
    No such file or directory
    LaTeX Error: File ''picins.sty'' not Found.
    GCC编译过程中的各种not declared in this scope
    20.有效的括号-力扣
    c++标准库bitset文件
    c++标准库windows.h文件
    warning: control reaches end of non-void function
    c++标准库iostream文件
  • 原文地址:https://www.cnblogs.com/wuanran/p/13584242.html
Copyright © 2011-2022 走看看