某次模拟赛 交换

题目描述 Description

给定一个{0, 1, 2, 3, … , n - 1}的排列 p。一个{0, 1, 2 , … , n - 2}的排列 q 被认为是优美的排列,当且仅当 q 满足下列条件:
对排列 s = {0, 1, 2, 3, ..., n - 1}进行 n – 1 次交换。
1. 交换 s[q0],s[q0 + 1]
2. 交换 s[q1],s[q1 + 1]

最后能使得排列 s = p.
问有多少个优美的排列,答案对 10^9+7 取模。

输入描述 Input Description

第一行一个正整数 n.
第二行 n 个整数代表排列 p.

输出描述 Output Description

仅一行表示答案。

样例输入 Sample Input

3
1 2 0

样例输出 Sample Output

1

数据范围及提示 Data Size & Hint

 30%: n <= 10

100%: n <= 50

之前的一些废话:《摔跤吧爸爸》这部电影真是太感动了

题解:还是觉得这种题挺难的,考试时候想不到正解的话花几分钟把30分拿了其实也挺爽的。

控制交换次数为n-1,所以我们可以这么考虑:考虑倒着处理,比如交换 (i, i + 1),那么前面的所有数不管怎么交换都无法到后面去(下标恒小于等于 i),后面的数也是一样到不了前面。说明这最后一次交换前,就要求对于所有的 x <= i, y > i,px<py。所以交换前左边的数是连续的,右边也是连续的。由于交换前,前面和后面的数是互相不干涉的,所以就归结成了两个子问题。(这段话是搬运题解而来的)

想到这里,就不太难了,很明显的区间DP:dp[i][j]表示将当前序列的第i个数到第j个数换成最终序列的方案数,只要区间长度确定了,那么交换次数也确定了,所以没有必要再单开一个状态来存交换次数。根据区间DP的套路,转移肯定是枚举中间点,但是还有一个限制:假如要在[L,R]中选取第k和第k+1个数进行交换,那么交换之后[L,K]中的数是再也不可能换到K以后了的,[K+1,R]中的数也不可能换到K+1以前了,所以需要保证交换第k和第k+1个数后,新序列中[L,K]的数都小于等于K,而[K+1,R]的数都大于K。只有满足以上限制的才能进行转移。转移方程是:dp[L,R]+=dp[L,K]*dp[K+1,R]*C(R-L,K-L-1),至于为什么要乘上一个组合数...自己想一想吧。

然后需要一个组合数预处理。

代码:

#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
#define mem(a,b) memset(a,b,sizeof(a))
inline int read()
{
    int x=0,f=1;char c=getchar();
    while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    while(isdigit(c)){x=x*10+c-'0';c=getchar();}
    return x*f;
}
const int maxn=55,MOD=1000000007;
int n,p[maxn],seq[maxn],tmp[maxn],cnt,dp[maxn][maxn],c[maxn][maxn];
bool has[maxn];
void dfs(int step)
{
    if(step==n)
    {
        for(int i=1;i<=n;i++)tmp[i]=p[i];
        for(int i=1;i<n;i++)swap(tmp[seq[i]],tmp[seq[i]+1]);
        for(int i=1;i<=n;i++)if(tmp[i]!=i-1)return;
        cnt++;
        return;    
    }
    for(int i=1;i<n;i++)
        if(!has[i])
        {
            seq[step]=i;has[i]=1;
            dfs(step+1);
            seq[step]=has[i]=0;
        }
}
int C(int n,int m)
{
    if(c[n][m])return c[n][m];
    int &ret=c[n][m];
    if(m==0)return ret=1;
    if(n<m)return ret=0;
    return ret=(C(n-1,m)+C(n-1,m-1))%MOD;
}
int DP(int L,int R)
{
    if(dp[L][R]>0)return dp[L][R]; 
    if(L==R)return dp[L][R]=1;
    if(R-L==1)
    {
        if(L<p[L] && R>p[R])dp[L][R]=1;
        else dp[L][R]=0;
        return dp[L][R];
    }
    for(int i=L;i<R;i++)
    {
        bool ok=0;
        swap(p[i],p[i+1]);
        for(int j=L;j<=i;j++)if(p[j]>i){ok=1;break;}
        for(int j=i+1;j<=R;j++)if(p[j]<i+1){ok=1;break;}
        if(ok){swap(p[i],p[i+1]);continue;}
        dp[L][R]=(dp[L][R]+(LL)DP(L,i)*(LL)DP(i+1,R)%MOD*C(R-L-1,i-L))%MOD;
    //    printf("%d %d %d %d %d\n",L,R,i,(LL)DP(L,i)*(LL)DP(i+1,R)%MOD*C(R-L-1,i-L),dp[L][R]);
        swap(p[i],p[i+1]);
    }
    if(dp[L][R]==-1)dp[L][R]=0;
    return dp[L][R];
}
int main()
{
    freopen("swap.in","r",stdin);
    freopen("swap.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++)p[i]=read()+1;
    DP(1,n);
  //  for(int i=1;i<=n;i++)for(int j=i;j<=n;j++)printf("dp%d %d %d\n",i,j,dp[i][j]);
    printf("%d\n",dp[1][n]%MOD);
    return 0;
}
/*
3
1 2 0

5
2 0 4 1 3


*/
View Code

 

总结:突破口在于控制交换次数,算是一种经验吧。

posted @ 2017-08-13 16:43  小飞淙的云端  阅读(330)  评论(0编辑  收藏  举报