交换 题解
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;
}

浙公网安备 33010602011771号