「CF1628A」Meximum Array
题目简介
将 \(\mbox{MEX}\) 定义为最小的未在某个集合中出现的非负整数
操作步骤如下:
\(\ \ 1.\) 从 \(a\) 数组中取出前 \(k\) 个数;
\(\ \ 2.\) 将这 \(k\) 个数的 \(\mbox{MEX}\) 放入 \(b\) 数组末尾;
\(\ \ 3.\) 从 \(a\) 数组中删除这 \(k\) 个数;
\(\ \ 4.\) 如果 \(a\) 不为空,重复执行 \(1\)~\(4\)。
求 \(b\) 数组的最大字典序。
分析
想要字典序尽可能大,需要满足如下条件:
\(\ \ 1.\) 排在前面的数字尽可能大;
\(\ \ 2.\) 数组长度尽可能大。
由此,制定贪心策略:当 \(a\) 的 \(\mbox{MEX}\) 值为 \(x\) 时,尽可能选择最短的 \(\mbox{MEX front} = x\) 前驱。
这样能够满足在 \(a\) 的当前 \(\mbox{MEX}\) 已定的情况下,\(a\) 后面的 \(\mbox{MEX}\) 尽可能大,并且 \(b\) 尽可能长。
具体如何实现?
设定一个 \(f\) 数组和一个 \(d\) 数组
\(f_i\):数字 \(i\) 在 \(a\) 的后继中出现了 \(f_i\) 次。
\(d_i\):数字 \(i\) 在 \(a\) 的前驱中出现了 \(d_i\) 次。
定义 \(mex\) 为当前序列的 \(\mbox{MEX}\),\(mex\_front\) 为序列前驱的 \(\mbox{MEX}\) ,\(mex\_ nxt\)为序列后继的 \(\mbox{MEX}\)
第一遍求出整个 \(a\) 数组的 \(mex\) ,然后进行第二遍扫描:
\(\ \ 1.\) 对 \(d_{a_i}+1\),对 \(f_{a_i}-1\)
\(\ \ 2.\) 求出当前 \(mex\_front\)
\(\ \ 3.\) 当发现 \(f_{a_i}=0\) ,则证明 \(a_i\) 不在 \(a\) 的后继中,更新 \(mex\_ nxt=\mbox{min}\ a_i\)
\(\ \ 4.\) 当发现 \(mex\_front = mex\),则将 \(mex\_front\) 计入答案中,让当前 \(mex=mex\_nxt\),让 \(mex\_front=0\),清空 \(d\) 数组,继续扫描。
\(AC\ Code\)
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int read(){
int x=0;
char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x;
}
const int Maxn=2e5+5;
int a[Maxn];
int f[Maxn];
int d[Maxn];
int ans[Maxn];
int main(){
int t=read();
while(t--){
memset(f,0,sizeof f);
memset(d,0,sizeof d);
int n=read();
int mex=0;
for(int i=1;i<=n;i++){
a[i]=read();
f[a[i]]++;
}
while(f[mex])mex++;
int mex_front=0,mex_nxt=mex;
int tot=0;
for(int i=1;i<=n;i++){
int &x=a[i];
d[x]++;
while(d[mex_front])mex_front++;
f[x]--;
if(!f[x])mex_nxt=min(mex_nxt,x);
if(mex_front==mex){
ans[++tot]=mex_front;
mex=mex_nxt;
for(int j=mex_front-1;j>=0;j--)
d[j]=0;
mex_front=0;
}
}
printf("%d\n",tot);
for(int i=1;i<=tot;i++)printf("%d ",ans[i]);
putchar('\n');
}
return 0;
}