牛客练习赛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应该要再套个分块或者线段树,否则这样纯暴力是可以被卡掉的,有空再学学怎么写更新上来吧(咕咕咕~