交换 题解

题目来源

Description

给定一个 \(0\sim n-1\) 的排列 \(p\)
定义一个 \(0\sim n-2\) 的排列 \(a\) 是合法的,当且仅当把排列 \(s={0,1,2...n-1}\) 按照以下方式操作后得到的恰好为排列 \(p\)

  • 从左到右遍历排列 \(a\) ,对于每一个 \(a_i\) 交换 \(s_{a_i}\)\(s_{a_i+1}\)

求有多少个 \(0\sim n-2\) 的排列合法。

输入格式

一行一个整数 \(n\)
一行 \(n\) 个整数表示排列 \(p\)

样例

\(in:\)

3
1 2 0

\(out:\)

1

数据范围

\(1\le n\le 3000\)

Solution

由于交换操作是一个排列,每两个位置之间只会交换一次,因此交换可以看作是“移动相邻位置两个数的不可逆操作”。
换一个思路,考虑每一个数到达正确的位置需要满足的条件。

  • 如果位置不变,那么不可能。
  • 如果位置改变,实际上是规定了这一段路径移动的先后顺序。

所求的排列与“每个移动位置的移动时间排列”是一一对应的。
而有了移动的先后顺序,也就相当于相邻两个移动的时间有了要求。
因此对移动时间的排列个数进行 \(dp\)
即:给定排列相邻两个元素大小关系,问有多少个合法排列。
\(dp[i][j]\) 表示前 \(i\) 个中 \(i\) 处于相对排名 \(j\) 的位置,前缀和优化可以做到 \(O(n^2)\)

#include<bits/stdc++.h>
#define E return cout<<'0',0;
#define F(i,j) for(int i=1;i<=j;i++)
#define N 55
#define mod 1000000007
using namespace std;
int n,p[N],pos[N],dp[N][N],pre[N],ans;
bool vis[N],rt[N];
inline int mo(int x){return x<mod?x:x-mod;}
int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n;
	F(i,n)cin>>p[i],p[i]++,pos[p[i]]=i;
	F(i,n){
		if(pos[i]==i)E
		if(pos[i]<i){
			if(i!=n&&vis[i-1]&&rt[i-1])E
			if(i!=n)vis[i-1]=1;
			if(pos[i]!=1&&vis[pos[i]-1]&&rt[pos[i]-1])E
			if(pos[i]!=1)vis[pos[i]-1]=1;
			for(int j=pos[i];j<=i-2;j++)
				if(vis[j]&&!rt[j])E
				else vis[j]=rt[j]=1;
		}else{
			if(i!=1&&vis[i-1]&&!rt[i-1])E
			if(i!=1)vis[i-1]=rt[i-1]=1;
			if(pos[i]!=n&&vis[pos[i]-1]&&!rt[pos[i]-1])E
			if(pos[i]!=n)vis[pos[i]-1]=rt[pos[i]-1]=1;
			for(int j=i;j<=pos[i]-2;j++)
				if(vis[j]&&rt[j])E
				else vis[j]=1;
		}
	}
	pre[1]=1;
	for(int i=2;i<=n-1;i++){
		F(j,i)dp[i][j]=rt[i-1]?pre[j-1]:mo(mod+pre[i-1]-pre[j-1]);
		F(j,i)pre[j]=mo(pre[j-1]+dp[i][j]);
	}
	F(i,n-1)ans=mo(ans+dp[n-1][i]);
	cout<<ans;
}
posted @ 2025-05-30 08:15  linjingxiang  阅读(163)  评论(0)    收藏  举报