AT_arc178_d [ARC178D] Delete Range Mex
[ARC178D] Delete Range Mex
题目翻译:
给定一个长度为 \(M\) 且值域为 \([1,N-1]\) 的整数序列 \(A\),及目标序列。我们要对为 \((0,1,2,…,N-1)\) 所组成的序列\(P\)的不同排序进行统计,统计有多少种排序使其在任意次数的操作下变为 \(A\)。
操作: 每次选择任意一个区间 \(l \sim r,(1 \le l \le r \le |P|)\) 并且如果 \(mex({ P_l ,P_{l+1},P_{l+2},…,P_r })\) 存在与 \(A\) 中,就删除它。
- 其中 \(mex\) 是指,对于一个由非负整数构成的集合 \(S\) ,\(mex(S)\) 表示不在集合 \(S\) 的最小非负整数
思路:
一.分析题目: 可以发现该题有以下性质:
\(1.\)若我想要删掉一个数 \(num\),那我一定要保证所给的 \(l \sim r\) 之间有 \(0 \sim num-1\) 这些数,并且不包含 \(num\) 。只有这样才能保证该区间的 \(mes\) 为 \(num\) 。因此我们的删除顺序一定是从大到小的
\(2.\) 也就是说所有比 \(num\) 小的数都在它的左侧,或者都在它的右侧。这样保证该数被删掉前,比他小的都没有删掉。
二.分析解法:
\(1.\) 我们的最终目标是要删掉一些数后变成 \(A\) 而前面又知道删除的顺序一定是从大到小的。那我们反过来,考虑加数,那加数的顺序也就是从小到大的
\(2.\) 因为 \(A\) 的大小为 \(M\) 那我们就有 \(M+1\) 个地方可以插入,中间空隙和左右两边。而且同一个空隙可以同时插入多个数
\(3.\) 我们假设 \(f[l][r][k]\) 表示在区间 \([l,r]\) 已经插入了数字 \(0 \sim k\)的方案数,并且保证该区间内不会在填出现比 \(k\) 大的数,也就是后续不能再填数到这个区间。理由是为了保证该状态的答案确定性,因为若多添加一个数,那改变的是 \(f[l][r][k+1]\)
\(4.\) 那我们从小到大枚举所有要加的点,若一个点已经出现在 \(A\) 间,那这个数原本就存在,也就不需要在有这个数的区间内加入这个数,那它的方案数也就没变。那我们令该数出现 \(pos_x\) 与 \(pos_{x+1}\) 之间的空隙间。那任意有该数的区间的答案都等于该区间不加这个数的答案,转移方程为:
\[\forall l,r \in[1,M+1], f[min(pos_x,l)][max(pos_x+1,r)][x]=f[l][r][x-1] \]
\(5.\)若一点不存在与 \(A\) 中那就这个数就被删除了,也就要加入:
- 若这个数要加到比他小的数的左侧,也就需要从之前右侧的状态转移过来。那转移方程为:
\[f[l][r][x]+= \sum_{i=l}^rf[i][r][x-1] \]
- 同理,若这个数要加到比他小的数的右侧,也就需要从之前左侧的状态转移过来。那转移方程为:
\[f[l][r][x]+= \sum_{i=l}^rf[l][i][x-1] \]
\(6.\)考虑特殊: 情况可以发现 \(0\) 比较特殊。继续分类讨论:
- 如果 \(0\) 已经出现,那很明显:
\[f[pos_0][pos_0+1][0]=1 \]
- 如果 \(0\) 还没出现,由于 \(0\) 是最小的非负整数,那任意正整数都可以将他删除。也就是说它可以插到到任意空隙,对答案没有影响:
\[\forall i \in [1,M+1],f[i][i][0]=1 \]最后只需要最开始给其初始化即可
\(7.\) 考虑优化: 第 \(5\) 点的转移可以由前缀和优化,维护。
最终答案也就是 \(f[1][M-1][N-1]\) ,总体的时间复杂度为 \(O(m^2n)\) ,考点区间dp
完整代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=510;
const int P=998244353;
int a[N],pos[N],f[N][N][N];
void add(int &a,int b){
a=(a+b)%P;
}
signed main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>a[i];
}
for(int i=1;i<=m;i++){
pos[a[i]]=i;
}
if(!pos[0]){
for(int i=1;i<=m+1;i++){
f[i][i][0]=1;
}
}
else{
f[pos[0]][pos[0]+1][0]=1;
}
for(int i=1;i<n;i++){
if(!pos[i]){
for(int r=1;r<=m+1;r++){
int il=0;
for(int l=r;l;l--){
add(il,f[l][r][i-1]);
add(f[l][r][i],il);
}
}
for(int l=1;l<=m+1;l++){
int il=0;
for(int r=l;r<=m+1;r++){
add(il,f[l][r][i-1]);
add(f[l][r][i],il);
}
}
}
else{
for(int l=1;l<=m+1;l++){
for(int r=l;r<=m+1;r++){
add(f[min(pos[i],l)][max(pos[i]+1,r)][i],f[l][r][i-1]);
}
}
}
}
cout<<f[1][m+1][n-1]<<endl;
}
由于做题时,借鉴了第一篇题解,所以思路和代码可能比较相似
区间 DP 讲解
本文作者:XichenOC
本文链接:https://www.cnblogs.com/XichenOC/p/18682396
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步