牛客练习赛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 = '\0'){
        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=='\n'||ioc=='\t';)ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(l=0;!(ioc==' '||ioc=='\n'||ioc=='\t'||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\n",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 = '\0'){
        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=='\n'||ioc=='\t';)ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(l=0;!(ioc==' '||ioc=='\n'||ioc=='\t'||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\n",min(pre[l-1],suf[r+1]));
	}
}

乱7788糟

本来就想着回顾一下莫队

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

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

posted @ 2020-08-30 02:39  AnranWu  阅读(228)  评论(0编辑  收藏  举报