[MX-S12-T3] 排列 题解
【MX-S12-T3】排列
考虑连续段 DP,但是这题的连续段与摩天大楼那题有区别,这题不能按数值从小到大插入,因为那样完全无法满足每个位置的条件。考虑按位置从小到大插入,记录其再值域上形成的连续段。(其实就是反过来了。)
然后就比较简单,代码里写了注释,有几个细节就是当只有一个连续段的时候有几个特殊情况要讨论一下。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define _int __int128
#define ull unsigned long long
#define pii pair<int,int>
#define fst first
#define scd second
#define pq priority_queue
#define mkp make_pair
#define popcount(x) __builtin_popcount(x)
#define endl '\n'
int n;
const int N = 5e3+10,mod=998244353;
int op[N],f[N][2][2],g[N][2][2];
inline void add(int &a,int b){
a+=b;
if(a>=mod)a-=mod;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
cin>>op[i];
}
f[0][0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<=n;j++){
for(int p=0;p<2;p++){
for(int q=0;q<2;q++){
g[j][p][q]=f[j][p][q];
f[j][p][q]=0;
}
}
}
for(int j=0;j<=n;j++){
for(int p=0;p<2;p++){
for(int q=0;q<2;q++){
int gv=g[j][p][q];
if(op[i]==0){
if(p)continue;
if(j){//加入第一段最左边
add(f[j][0][q],gv);
add(f[j][1][q],gv);
}
add(f[j+1][1][q],gv);//新开一段,且是1
add(f[j+1][0][q],gv);//新开一段
if(!j&&!q){//如果没有段,新开一段有可能是n
add(f[j+1][p][1],gv);
}
}else if(op[i]==1){
if(q)continue;
if(j){//加入最后一段最右边
add(f[j][p][0],gv);
add(f[j][p][1],gv);
}
add(f[j+1][p][1],gv);//新开一段,且是n
add(f[j+1][p][0],gv);//新开一段
if(!j&&!p){//如果没有段,新开一段有可能是1
add(f[j+1][1][q],gv);
}
}else if(op[i]==2){//后面所有数比他小,应占据目前最左段
if(!p){//必须是1
if(j)add(f[j][1][q],gv);
add(f[j+1][1][q],gv);
}else{//放第一段后面,且必须紧贴,不能新开段
if(j){
if(j>1)add(f[j][p][q],gv);//有大于1个段直接放第一段最后
else if(!q){//只有一个段则右边不能封死
add(f[j][p][0],gv);
add(f[j][p][1],gv);
}
}
if(j>1){
add(f[j-1][p][q],gv);//合并1,2段
}
}
}else if(op[i]==3){
if(!q){
if(j)add(f[j][p][1],gv);
add(f[j+1][p][1],gv);
}else{
if(j){
if(j>1)add(f[j][p][q],gv);
else if(!p){
add(f[j][0][q],gv);
add(f[j][1][q],gv);
}
}
if(j>1)add(f[j-1][p][q],gv);
}
}
}
}
}
}
cout<<f[1][1][1];
return 0;
}

浙公网安备 33010602011771号