与 MEX 有关的题目
作者:互联网
MEX
MEX——某不知名战略游戏中墨西哥的国家 tag (不是
\(\text{MEX}\) 指的是不属于集合 \(S\) 中的最小非负整数,即
\[\text{mex}(S)=\min{x} \quad (x\notin S,x\in N) \]至于它有什么性质,我也不知道。但是这里整理了一些 OI 上的与 \(\text{MEX}\) 有关的题目,难度在普及提高之间。
MEX 与构造
CF739A Alyona and mex
你有 \(m\) 个区间,要求构造一个长度为 \(n\) 的序列使得这 \(m\) 个区间中 \(\text{mex}\) 最小的最大。
题解
因为一个区间 \([l_i,r_i]\) 最多只有 \(r_i-l_i+1\) 个数,所以
\[ans\leq \min_{i}^{m}\{r_i-l_i+1\} \]所以我们最好使得每个区间 \(\text{mex}=\min_{i}^{m}\{r_i-l_i+1\}=ans\) 。
构造这个序列,只需使得每个长度为 \(ans\) 的序列都包含 \([0,ans-1]\) 内的数恰好一次。
所以对于位置 \(i\) 输出 \(i\bmod ans\) 即可。
int main(){
ans=n=read(),m=read();
for(int i=1;i<=m;++i){
int x=read(),y=read();
ans=min(y-x+1,ans);
}
printf("%d\n",ans);
for(int i=1;i<=n;++i)
printf("%d ",i%ans);
putchar('\n');
return 0;
}
LG6852 Mex
需要构造一个 \(0\sim n\) 的排列(下标从 \(0\) 开始),给定 \(m\) 条信息,每条信息形如 \((l_i,r_i,val_i)\),表示区间 \([l_i,r_i]\) 的 \(\text{mex}\) 值为 \(val_i\) 。
题解
考虑每条信息的限制。 \((l_i,r_i,val_i)\) 意味这 \([l_i,r_i]\) 的区间内必须包含 \([0,val_{i-1}]\) 的所有数(\(val_i\neq 0\)),\([l_i,r_i]\) 的区间内必定不包含数 \(val_i\)。满足这些信息和答案是充分且必要的关系。
特别的,对于 \(val_i=0\) 的信息,我们特殊处理,即利用数据结构(或其它思想),标记段 \([l_i,r_i]\) 不能包含 \(0\) 。
因此定义 \([L_i,R_i]\) 为数 \(i\) 必须存在的区间,\([nL_i,nR_i]\) 为数 \(i\) 必须不存在的区间。注意此时 \(i\neq 0\)。则根据第一段的内容,可以得出。
\[\forall j\in [1,val_i-1] \quad L_j=\max(L_j,l_i),R_j=\min(R_j,r_i) \]因为这是个排列,每个数都不一样,所以对于两条信息 \((l_i,r_i,val_i),(l_j,r_j,val_i)\),必须有 \(\{0,1,2,\dots,val_i-1\}\in [l_i,r_i]\cup [l_j,r_j]\),则可以得出
\[nL_{val_i}=\min(nL_{val_i},l_i),nR_{val_i}=\max(nR_{val_i},r_i) \]自此,只要每个值 \(i\) 的位置均满足这些信息,这个排列就能构造。根据以上的形式,容易发现,包含 \(val_i\) 的区间一定大于等于包含 \(val_{i-1}\) 的区间,那么就需要从小到大地填入数字,这样一定是最优的。
为了优化时间复杂度,可以使用并查集维护一个已经填数的位置后的下一个空位在那里。虽然严格的时间复杂度我不会想证,但是容易发现除了填 \(0\) 时需要扫一整个序列,对于其它的数,在考虑各种限制并且使用并查集跳已经填的位置后,又是只要能填就填,时间复杂度已经是近乎 \(O(1)\) 的了(当然并查集的复杂度就不是 \(O(1)\))。
一些具体实现见代码。
const int N=1000006;
int n,m,sum[N],l[N],r[N],nl[N],nr[N],ans[N],fa[N];
inline int fidf(int x){return x==fa[x]?x:fa[x]=fidf(fa[x]);}
inline void merge(int x,int y){
int fx=fidf(x),fy=fidf(y);
if(fx!=fy)fa[fx]=fy;
}
int main(){
n=read(),m=read();
for(int i=0;i<=n;++i)nl[i]=n+1,r[i]=n;//注意是一个0到n,而不是0到n-1的排列
for(int i=1;i<=m;++i){
int L=read(),R=read(),x=read();
nl[x]=min(nl[x],L),nr[x]=max(nr[x],R);
if(x==0)++sum[L],--sum[R+1];//差分来区间加,若某位置有值则说明这个位置不能填0
else l[x-1]=max(l[x-1],L),r[x-1]=min(r[x-1],R);
}
for(int i=n-1;i>=0;--i){//由mex的性质可推得
l[i]=max(l[i],l[i+1]);
r[i]=min(r[i],r[i+1]);
}
for(int i=1;i<=n;++i)fa[i]=i;
for(int i=0;i<=n;++i){//特判0能否填
if(i)sum[i]+=sum[i-1];
if(l[0]<=i&&i<=r[0]&&!sum[i])
{ans[i]=0;merge(i,i+1);goto step;}
}
return puts("-1"),0;
step:
for(int i=1;i<=n;++i){
for(int j=l[i];j<=r[i];++j){
j=fidf(j);//找空位,节省时间
if(nl[i]<=j&&j<=nr[i]){j=nr[i];continue;}
if(j<=r[i]){ans[j]=i;merge(j,j+1);goto end;}
}
return puts("-1"),0;
end:;
}
for(int i=0;i<=n;++i)
printf("%d ",ans[i]);
putchar('\n');
return 0;
}
// 229ms / 22.14MB / 1.43KB C++14 (GCC 9) O2
CF1619E MEX and Increments
给你一个数列,每次可以花费 \(1\) 代价将其中的一个数加一。从 \(0\) 到 \(n\) 分别输出至少要花费多少才能使这个数是数列中没有出现的最小的非负整数。
sample.in
2
7
0 1 2 3 4 3 2
7
1 0 -1 -1 -1 -1 -1 -1
sample.out
1 1 2 2 1 0 2 6
题解
使得 \(\text{mex}\{a_1,a_2,\dots,a_n\}=i\),需要满足两条件
- \(0,1,2,\dots,i-1\) 均在 \(a\) 中出现
- \(i\) 不在 \(a\) 中出现。
满足后者只需将所有的 \(a_j=i\) 都加一即可。现在考虑前者如何计算。
假设现在需要满足 \(\text{mex}=i\) 。假设 \(bot_i\) 为 \(a_j=i\) 的数的个数, \(sum_i\) 为 \(bot_i\) 的前缀和,则一定要满足。
\[\forall j\in[1,i] \quad sum_{j-1}\geq j \]就是说比 \(i\) 小的数一定要填满 \([0,i-1]\) 的区间。
不妨假设 \([0,i-2]\) 的区间已经填满,并且有一个栈记录填满这些位置后剩余的一些可用的数。现在要填 \(i-1\) 这个位置,如果 \(bot_{i-1}\geq 1\),那么就用 \(a_k=i-1\) 填是最好的,并把其他的等于 \(i-1\) 的数加入这个栈,这样如果以后的位置 \(i+1\) 不能用原有的 \(a_k=i\) 来填,则可以将栈顶这个最接近 \(i\) 的数添上,这样操作的花费最小。如果 \(bot_i=0\) 正如刚才所言,用此时的栈顶元素填就好了。
具体见代码,比较清晰。
const int N=200005;
int n,a[N],stk[N],top,bot[N];
long long ans;//开long long
int main(){
int T=read();
while(T--){
n=read();top=0,ans=0;//次数的 ans 是将所有小于 i 的数组成的集合的值域完全包含 [0,i-1] 需要的操作次数
for(int i=1;i<=n;++i)
++bot[a[i]=read()];
printf("%d ",bot[0]);//特判0的答案
for(int i=1;i<=n;++i){//输出每个 i 作为 MEX 时的答案
for(int j=1;j<=bot[i-1];++j)
stk[++top]=i-1;
if(top==0){//如果所有比 i 小的数都不够 i 个,那么无解且后面一样无解
for(int j=i;j<=n;++j)
printf("%d ",-1);
break;
}
ans+=(i-1-stk[top--]);//将栈顶的元素变成i-1,使之符合MEX的要求,同时这个栈顶元素的值就固定了
printf("%lld ",ans+bot[i]);//别忘了答案由两部分组成,为 i 的所有数都必须 +1 才能满足此时的 i
}
putchar('\n');
for(int i=1;i<=n;++i)
bot[a[i]]=0;
}
return 0;
}
CF1375D Replace by MEX
给定一个序列 \(A\),每次操作可以选定一个位置 \(p\),令 \(a_p\) 为这个序列的 \(\text{mex}\) 。你需要进行若干次操作,使得这个序列单调不降。操作次数不能超过 \(2\times n\)。数据范围:\(1\leq n\leq 10^3\)。
题解
考虑到操作次数限定得并不是很严,又是每次赋值为 \(\text{mex}\) 的操作,考虑每次最终的序列变成 \(0,1,2,\dots,n-1\) 的形式。
所以如果对于一个位置 \(i\),\(a_i=i-1\),那么这个位置的数就不用修改了,并且这个数列的 \(\text{mex}\) 也不会取到 \(i-1\)。不妨称这个数确定。
考虑两种情况。
- 当前 \(\text{mex}<n\),那么我们可以将 \(a_{\text{mex}+1}\) 替换为 \(\text{mex}\),然后这个数以后就不用修改了。
- 当前 \(\text{mex}=n\),那么我们将任意一个还未确定的数变成 \(n\)。那么经过一次这样的操作,下一次的情况一定是 \(\text{mex}<n\)。
考虑这样的操作次数。
每进行一次 1 操作就会确定一个数,每进行一次 2 操作紧接着就会进行一次 1 操作。最多确定 \(n\) 个数。所以操作次数一定小于 \(2n\) 。
代码实现
因为 \(n\leq 10^3\),大可直接暴力求 \(\text{mex}\),和暴力找未确定的数。
const int N=1003;
int n,a[N],stk[N*2],top,lef;bool sta[N],vis[N];
inline int qrmex(){
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;++i)
vis[a[i]]=1;
for(int i=0;i<=n;++i)
if(!vis[i])return i;
return 114514;
}
int main(){
int T=read();
while(T--){
lef=n=read(),top=0;
for(int i=1;i<=n;++i)a[i]=read();
memset(sta,0,sizeof(sta));
for(int i=1;i<=n;++i)
if(a[i]==i-1){sta[i]=1;--lef;}
while(lef){
int mex=qrmex();
if(mex==n){//当前无法使一个数能达到目标状态,就先将这个数变成 n ,这样会空出一个位置来
for(int i=1;i<=n;++i)
if(!sta[i]){stk[++top]=i,a[i]=n;break;}
}else{//否则就将这个数固定在目标状态,以后不再管它
stk[++top]=mex+1;
a[mex+1]=mex;
sta[mex+1]=1;
--lef;
}
}
printf("%d\n",top);
for(int i=1;i<=top;++i)
printf("%d ",stk[i]);
putchar('\n');
}
return 0;
}
MEX 与计算
尝试枚举 \(\text{mex}\) 哦。
LG8445 射命丸文的取材之旅
给定序列 \(\{a_n\},\{b_n\}\),求一个序列 \(\{c_n\}\) 满足 \(\forall i\in[1,n],c_i\in\{a_i,b_i\}\),最大化
\[\max\{r-l+1-\operatorname{mex}\{c_l,c_{l+1},\dots, c_{r-1},c_r\}\}(1\le l\le r\le n) \]并输出该式子可能的最大值。
题解
不妨先令 \(\{c_n\}\) 固定,即 \(\{a_n\}=\{b_n\}=\{c_n\}\),来求此时的答案。
我们可以枚举每个值 \(i\) 作为答案的 \(\text{mex}\) 值,那么只需要找到不包含 \(i\) 这个值的区间的最长长度,长度减去 \(i\) 就是此时答案。
对于答案的计算式,发现在区间长度确定的情况下,\(\text{mex}\) 越小,答案越大。所以对于同一个区间 \([l,r]\),假设 \(x,y(x<y)\) 两数均不在其中存在,则答案是 \(r-l+1-x\) 。因此,枚举 \(\text{mex}\) 值的的时候,无需考虑不包含此值的区间的真实 \(\text{mex}\) 值是否为它。
现在使得 \(c_i\in\{a_i,b_i\}\)。根据上方的结论发现,如果 \(a_i\neq b_i\),那么假如答案的 \(\text{mex}\) 值为 \(a_i\) 或 \(b_i\),则一定可以通过将 \(c_i\) 变成 \(b_i\) 或 \(a_i\) 的方法,将答案区间得以跨越 \(i\) 这个位置得以延伸。所以在此种情况下,无论答案的 \(\text{mex}\) 值取谁,\(i\) 这个位置一定可以答案区间内取到。
代码实现
可以使用 vector
方便地存储每个值的位置。对于 \(a_i\neq b_i\) 的情况,只需要将 \(c_i\) 赋值为 \(n+2\),即一个不会被考虑到的值即可。
const int N=1000006,INF=0x3f3f3f3f;
int n,a[N],b[N],c[N],ans=-INF;
vector<int>lis[N];
int main(){
n=read();
for(int i=1;i<=n;++i)a[i]=read();
for(int i=1;i<=n;++i)b[i]=read();
for(int i=1;i<=n;++i){
if(a[i]==b[i])
c[i]=a[i];
else c[i]=n+2;
}
for(int i=1;i<=n;++i)
lis[c[i]].push_back(i);
for(int i=0;i<=n;++i){//枚举mex
int siz=lis[i].size(),len=0;
for(int j=1;j<siz;++j){
len=lis[i][j]-lis[i][j-1]-1;
ans=max(ans,len-i);
}
if(siz){
ans=max(ans,lis[i][0]-1-i);
ans=max(ans,n-lis[i][siz-1]-i);
}else ans=max(ans,n-i);
}
printf("%d\n",ans);
return 0;
}
CF1527 MEX Tree
给出一棵 \(n\) 个点的树,点从 \(0\) 到 \(n - 1\) 编号。定义一条路径的权值是路径上所有点编号的 \(\operatorname{mex}\)。对于每个 \(0\le i\le n\) 求出 \(\operatorname{mex}\) 为 \(i\) 的路径有几条。注意,这里统计的路径需要包括至少一条边。
题解
此题有更为优美的线性做法,但是这里位置太小了写不下我太菜了。这里提供一种需要求 \(lca\) 的 \(O(n\log n)\) 做法。来自这位大佬。
包含 \([0,i-1]\) 的路径数减去包含 \([0,i]\) 的路径数是包含 \([0,i-1]\) 且不包含 \(i\) 的路径数,即 \(\text{mex}=i\) 的路径数。
定义 \(ans_i\) 为包含 \([0,i]\) 的路径数,则 \(\forall i\geq 1\),有 \(\text{mex}_i=ans_{i-1}-ans_i\)。特别的,有 \(\text{mex}_i=\frac{n\times (n-1)}{2}-ans_0\)。
将这棵树转化成一颗以 \(0\) 为根的有根树。考虑一个最短的包含 \([0,i-1]\) 的路径,左右端点分别是 \(l,r\)。三种情况:
- \(i\) 在 \(l\) 或 \(r\) 的子树中,那么将对应的端点移动到 \(i\)。
- \(i\) 在 \(l\) 到 \(r\) 的路径上,那么无需任何操作。
- 发现除了以上两种情况外的所有情况,都无法找到一条简单路径满足 \([0,i]\) 均包含其中。那么 \(\forall j\geq i, ans_j=0\) 。
考虑如何计算前两种情况的 \(ans\)。
- 当 \(l,r\) 均在 \(0\) 的子树内时,答案即是 \(siz_l\times siz_r\)。
- 当 \(l,r\) 其中一个为根的时候,不妨设 \(l\) 不为根,\(l\) 在 \(0\) 的子树 \(v\) 中,答案就是 \(siz_l\times (n-siz_v)\)。
代码
使用树剖实现各类树上操作,倍增也行。其它很多方法也都可以。
const int N=200005;
int n;ll ans[N];
vector<int>edge[N];
int dep[N],fat[N],son[N],tps[N];ll siz[N];
void dfs1(int u,int f){
siz[u]=1,dep[u]=dep[f]+1,fat[u]=f;son[u]=0;//多组数据清空son!!!
for(auto v:edge[u]){
if(v==f)continue;
dfs1(v,u);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]])son[u]=v;
}
}
void dfs2(int u,int t){
tps[u]=t;
if(son[u])dfs2(son[u],t);
for(auto v:edge[u]){
if(v==son[u]||v==fat[u])continue;
dfs2(v,v);
}
}
inline int qrlca(int x,int y){
while(tps[x]!=tps[y]){
if(dep[tps[x]]<dep[tps[y]])swap(x,y);
x=fat[tps[x]];
}return dep[x]<dep[y]?x:y;
}
inline ll getsiz(int loc){//暴力跳父亲应该是能hack掉的
ll tmp=siz[loc];
while(fat[tps[loc]]>1)
loc=fat[tps[loc]],tmp=siz[loc];
while(fat[loc]>1)
loc=fat[loc],tmp=siz[loc];
return tmp;
}
int main(){
int T=read();
while(T--){
n=read();
for(int i=0;i<=n;++i)edge[i].clear();
for(int i=0;i<=n+1;++i)ans[i]=0;//ans会计算到n+1
for(int i=1;i<n;++i){
int u=read()+1,v=read()+1;
edge[u].push_back(v);
edge[v].push_back(u);
}
dfs1(1,0);dfs2(1,1);
ll tmp=1;
for(auto v:edge[1]){ans[1]+=tmp*siz[v];tmp+=siz[v];}
int l=1,r=1;ll sizl=0,sizr=0;
for(int i=2;i<=n;++i){
int a=qrlca(i,l),b=qrlca(i,r);
if(a==l&&b==1){
if(l==1)sizl=getsiz(i);
l=i;
}else if(a==1&&b==r){
if(r==1)sizr=getsiz(i);
r=i;
}else if(a!=i&&b!=i)break;
if(l==1)ans[i]=siz[r]*(n-sizr);
else if(r==1)ans[i]=siz[l]*(n-sizl);
else ans[i]=siz[l]*siz[r];
}
printf("%lld ",(ll)n*(n-1)/2-ans[1]);
for(int i=2;i<=n+1;++i)printf("%lld ",ans[i-1]-ans[i]);
putchar('\n');
}
return 0;
}
MEX 与 DP
CF1613D MEX Sequences
定义一个整数序列 \(x_1,x_2,\dots,x_k\) “\(\text{MEX}\) 正确”,当且仅当:
\[\forall i\in[1,k] \quad |x_i-\text{MEX}\{x_1,x_2,\dots,x_i\}| \leq 1 \]现在给定一个由 \(n\) 个非负整数组成的数组 \(a\),计算给定数组的非空 “\(\text{MEX}\) 正确”子序列的个数。答案对 \(998244353\) 取模。
题解
由于是子序列,我们可以考虑将 \(x_i\) 加入或不加入对状态的印象,所以定义状态的时候,可以省去下标 \(i\) 这一维度。
定义 \(dp_{i,0}\) 表示使当前序列 \(\text{MEX}\) 取 \(i\),且序列不包含 \(i+1\) 这一数的方案数,\(dp_i,1\) 则表示包含 \(i+1\) 这一数的方案数。初始状态 \(dp_{0,0}=1\) 。意思是空序列 \(\text{MEX}=0\) 且构成方案只有一种。
假设当前加入的数为 \(x_i\),则根据 \(\text{MEX}\) 的定义,是无法转移到 \(dp_{x_i}\) 的状态的,只能转移到状态 \(dp_{x_i-1,1},dp_{x_i+1,0},dp_{x_i+1,1}\) 这三个状态。
稍加分析可以得出以下转移
\[\begin{align} dp_{x_i-1,1}&\gets dp_{x_i-1,1}+(dp_{x_i-1,1}+dp_{x_i-1,0})\\ dp_{x_i+1,0}&\gets dp_{x_i+1,0}+(dp_{x_i+1,0}+dp_{x_i,0})\\ dp_{x_i+1,1}&\gets dp_{x_i+1,1}+(dp_{x_i+1,0}) \end{align} \]最后要给答案减一因为空序列不算答案。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
inline ll read(){
ll x=0,f=1;char ch=getchar();
while(ch<'0'||'9'<ch){if(ch=='-')f=-1;ch=getchar();}
while('0'<=ch&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return x*f;
}
const int N=500005;
const ll MOD=998244353;
int n;
ll dp[N][2],ans;
int main(){
int T=read();
while(T--){
n=read();ans=0;
for(int i=0;i<=n+2;++i)
dp[i][0]=dp[i][1]=0;
dp[0][0]=1;
for(int i=1;i<=n;++i){
int x=read();
//dp_{x,0/1} 表示到当前位置 mex 值取 x ,无/有 x+1 的方案数
dp[x+1][0]=(dp[x+1][0]+(dp[x+1][0]+dp[x][0])%MOD)%MOD;
dp[x+1][1]=(dp[x+1][1]+(dp[x+1][1]))%MOD;
dp[x-1][1]=(dp[x-1][1]+(dp[x-1][1]+dp[x-1][0])%MOD)%MOD;
//可以考虑加入这个数或不加入这个数,第二个括号内即是加入这个数的贡献
}
dp[0][0]=(dp[0][0]-1+MOD)%MOD;//对于最终的答案而言是不合法的
for(int i=0;i<=n;++i)
ans=(ans+dp[i][0]+dp[i][1])%MOD;
printf("%lld\n",ans);
}
return 0;
}
标签:题目,int,text,有关,mex,ans,MEX,dp 来源: https://www.cnblogs.com/BigSmall-En/p/16526110.html