【题目描述】
给定一个{0,1,2,3,…,n-1}的排列 p。
一个{0,1,2,…,n-2}的排列q被认为是优美的排列,当且仅当q
满足下列条件:
对排列s={0,1,2,3,...,n-1}进行n–1次交换。
①交换s[q0],s[q0+1]。
②交换s[q1],s[q1+1]。
……
最后能使得排列s=p。
问有多少个优美的排列,答案对1e9+7取模。
【输入】
第一行一个正整数n。
第二行n个整数代表排列p。
【输出】
仅一行表示答案。
【输入样例】
3
1 2 0
【输出样例】
1
【样例解释】
q={0,1}{0,1,2}→{1,0,2}→{1,2,0}
q={1,0}{0,1,2}→{0,2,1}→{2,0,1}
【数据规模】
对于30%的数据,n≤10。
对于100%的数据,n≤50。
【题解】
可以考虑变为将p数组变为{0,1,……,n-1}的q数组有好多个。
令f[i][j]表示把原序列中的i个数{j,j+1,……,j+i-1}排列成升序的方案数。则答案为f[n][0]。
转移:
发现每个i和i+1只能交换一次,所以换了后两边两个区间相对独立。
枚举断点,在i右边k-1个,若交换后i-k段内数为i-k且k-j段内数为k-j则可以交换,f[i][n]=inom{i-2}{k-1}*f[k][n]*f[i-k][n+k]。(乘以inom{i-2}{k-1}是选k-1次在左边交换)。
可以预处理出对于[i,j]段内所有数的相对位置,例如数列p 3 0 1 2 的yc[1][3]即为{3,1,2}。这样就可以判断交换后是否合法。(因为设此时区间为i,j如果交换在同在左区间两个数(即改变相对位置)则左右区间内的数肯定不合法,则预处理出相对位置可行)
代码如下:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mo=1e9+7;
const int N=55;
int C[N][N],f[N][N],n,p[N],xi[N][N][N];//xi表示[i,j]的相对位置排列
inline void yclxi()
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
int tot=0;
for(int k=1;k<=n;k++)
if(p[k]>=i&&p[k]<=j)
xi[i][j][i+(tot++)]=p[k];
}
for(int i=0;i<=n;i++) C[i][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%mo;
}
inline bool check(int l,int r,int mid)
{
for(int i=l;i<=mid-1;i++) if(xi[l][r][i]>mid) return 0;
if(xi[l][r][mid+1]>mid) return 0;
for(int i=mid+2;i<=r;i++) if(xi[l][r][i]<=mid) return 0;
if(xi[l][r][mid]<=mid) return 0;
return 1;
}
inline int dp(int go,int co)//dp[i][j]表示有i个数:(从j到j+i-1) 排成升序的方案数。
{
if(f[go][co]) return f[go][co];
if(go==1)
{
f[go][co]=1;
return 1;
}
int daan=0;
for(int i=1;i<=go-1;i++)
{
if(check(co,co+go-1,co+i-1))
daan+=C[go-2][i-1]*dp(i,co)%mo*dp(go-i,co+i)%mo,daan%=mo;
}
f[go][co]=daan;
return daan;
}
signed main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>p[i],p[i]++;
yclxi();
cout<<dp(n,1);
return 0;
}