• 某次模拟赛 交换


    题目描述 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
    ",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
    ",i,j,dp[i][j]);
        printf("%d
    ",dp[1][n]%MOD);
        return 0;
    }
    /*
    3
    1 2 0
    
    5
    2 0 4 1 3
    
    
    */
    View Code

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

  • 相关阅读:
    前端知识体系
    DOMContentLoaded与load的区别
    最佳网页宽度及其实现
    一些颜色工具网站
    Firebug入门指南
    CSS中背景图片定位方法
    字符编码笔记:ASCII,Unicode 和 UTF-8
    学JS的书籍
    深入理解定位父级offsetParent及偏移大小
    event——事件对象详解
  • 原文地址:https://www.cnblogs.com/FYH-SSGSS/p/7353901.html
Copyright © 2020-2023  润新知