P14568 【MX-S12-T3】排列题解

P14568 【MX-S12-T3】排列

前言

本题解思路来自梦熊直播视频讲解,本文将进行完整的讲解且给出代码。

思路

对于由 \(op_i=\{0,1\}\)\(op_i=\{2,3\}\) 构成的两个序列,他们内部之间的相对关系是确定的,可以将两个序列根据相对大小来从小到大提取出来分别为序列 \(a\) 和序列 \(b\)

接下来将序列的每个数赋值,对于数字 \(1\)能且仅能赋给 \(a_1\)\(b_1\),接下来数字 \(2\),可以赋给 \(a_2\)\(b_1\) 及另一种情况 \(a_1\)\(b_2\),以此类推。可以设 dp 状态为 \(f_{i,j}\) 表示 \(a\) 序列中已赋值了前 \(i\) 个数,\(b\) 序列中已赋值了前 \(j\) 个数的方案数

大部分的转移方程为

\[f_{i+1,j}=f_{i+1,j}+f_{i,j} \\ f_{i,j+1}=f_{i,j+1}+f_{i,j} \]

但有不合法情况时不能转移,因为我们是从小到大填数,当填的下一个数为前缀最小值时,如果在这个位置左边已填过后缀最大值时,这个位置就没法填。同理当我们填下一个后缀最小值时,看右边是否填过一个前缀最大值

当然判断是否合法可以预处理\(prea_i\) 为填了前 \(i\)\(a_j\)\(op_j=1\)最右的位置,\(preb_i\) 为填了前 \(i\)\(b_j\)\(op_j=3\)最左的位置。

部分应判断是否合法的转移方程为

\[f_{i+1,j}=f_{i+1,j}+f_{i,j},preb_j>a_{i+1},op_{a_{i+1}}=0 \\ f_{i,j+1}=f_{i,j+1}+f_{i,j},prea_i<b_{j+1},op_{b_{j+1}}=2 \]

代码

#include<bits/stdc++.h>
#define ll long long
#define int ll
using namespace std;
const int N=1e6+10;
const int mod=998244353;
const int INF=1e18+7;

int n;
deque<int> q;
deque<int> p;

int a[N];
int b[N];
int c[N];

int f[5005][5005];

int preb[N];
int prea[N];

void solve(){
	cin>>n;
	
	for(int i=1;i<=n;i++){
		cin>>c[i];
		if(c[i]==0){
			q.push_front(i);//小的放前 
		}
		else if(c[i]==1){
			q.push_back(i);//大的放右 
		}
	}
	for(int i=n;i>=1;i--){//倒着枚举 
		if(c[i]==2){
			p.push_front(i);//小的放左 
		}
		else if(c[i]==3){
			p.push_back(i);//大的放右 
		}
	}
	//判断是否存在不可能赋值的情况 
	int op=0;
	for(int i=1;i<=n;i++){
		for(int j=i+1;j<=n;j++){
			if(c[i]==3&&c[j]==1){
				op=1;
			}
			if(c[i]==2&&c[j]==0){
				op=1;
			}
		}
	}
	if(op){
		cout<<0;
		return;
	}
	//构建a,b序列(储存位置下标) 
	int lena=0,lenb=0;
	while(!q.empty()){
		a[++lena]=q.front();
		q.pop_front();
	}
	
	while(!p.empty()){
		b[++lenb]=p.front();
		p.pop_front();
	}
	//预处理 
	prea[0]=0;
	for(int i=1;i<=lena;i++){
		prea[i]=prea[i-1];
		if(c[a[i]]==1){//前缀最大值 
			prea[i]=max(prea[i-1],a[i]);//最靠右的位置 
		}
	}
	preb[0]=INF;
	for(int i=1;i<=lenb;i++){
		preb[i]=preb[i-1];
		if(c[b[i]]==3){//后缀最大值 
			preb[i]=min(preb[i-1],b[i]);//最靠左的位置 
		}
	}
	
	f[0][0]=1;
	for(int i=0;i<=lena;i++){
		for(int j=0;j<=lenb;j++){
			if(c[a[i+1]]==1||preb[j]>a[i+1]){//最小前缀前有最大的后缀 
				f[i+1][j]+=f[i][j];
				f[i+1][j]%=mod;
			}
			if(c[b[j+1]]==3||prea[i]<b[j+1]){//最小后缀后有最大的前缀
				f[i][j+1]+=f[i][j];
				f[i][j+1]%=mod;
			}
		}
	} 
	cout<<f[lena][lenb];
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(nullptr);   
	solve();
    return 0;
}
posted @ 2025-11-24 14:39  sad_lin  阅读(6)  评论(0)    收藏  举报