• P2401 不等数列


    原题链接 https://www.luogu.org/problemnew/show/P2401

    第一眼看觉得是暴力,草草得估了一下下时间复杂度:O(n * n!),显然不行。

    考虑DP。

    我们设:dp [ i ][ j ] 为序列中有 i 个数,其中满足 j 对 '<' 关系;

    考虑边界情况

    当我们的序列是从大到小排列时,无论序列里有多少个数,都是满足 '>' 关系,不满足 '<'关系;

    换句话说,也就是使得序列全都不满足 '<' 关系的情况也就一种排列;

    那么我们就得出了边界条件:

        for(int i=1;i<=n;i++)     //当前序列里有i个数 
            dp[i][0]=1;           //只能从大到小排好,就一种情况

    考虑状态转移方程

    假设我们的序列里已经排好了 i-1 个数,我们当前正在插入第 i 个数,考虑我们可以往哪里插入:

    加入我们已经将X1~X5排好了,它们的关系是这样的:

    (A)现在我们考虑插入一个X6,我们可以插在这个序列的最后边,那么就会变成这样子:

    (B)当然我们也可以插入到最前面,那么就是介个样子:

    然后就是插在序列中间的情况,还要细分:

    (C)1.插在小于号的位置:

    (D)2.插在大于号的位置:

    我们发现(A)和(D)这两种插法都会使得原序列多一个小于号,而(B)和(C)不会使原序列多一个小于号。

    所以我们dp [ i ][ j ] 就可以从 dp [ i-1 ][ j-1 ] 和 dp [ i-1 ][ j ] 转移过来了,分别对应的是增加一个小于号和不增加小于号的情况;

    怎么转移呢?先考虑 dp [ i-1 ][ j-1 ] 怎么转移过来: 

    序列中已经有了 i-1 个数,说明有 i-2 个符号('<' 或 '>'),其中有 j-1 个'<',那不就说明有(i-2)-(j-1)= i-j-1 个 '>',我们要使这个数按照(A)和(D)的方式插入才能转移到 dp [ i ][ j ],所以考虑到我们合法的插入位置有(i-j-1)+1 = i-j 个,根据乘法计数原理可知:产生的方案数为 dp [ i-1 ][ j-1 ] *(i-j);

    dp [ i-1 ][ j ]同理,我们要将其按照(B)和(C)的方式插入,i-1 个数,j 个'<',合法的插入位置有 j+1 个,根据乘法技术原理可知:产生的方案数为 dp [ i-1 ][ j ] *(j+1);

    考试时的AC代码:

    #include<iostream>
    #include<cstdio>
    using namespace std;
    int read()
    {
        char ch=getchar();
        int a=0,x=1;
        while(ch<'0'||ch>'9')
        {
            if(ch=='-') x=-x;
            ch=getchar();
        }
        while(ch>='0'&&ch<='9')
        {
            a=(a<<1)+(a<<3)+(ch-'0');
            ch=getchar();
        }
        return a*x;
    }
    const int mod=2015;
    int n,k;
    int dp[1010][1010];
    //DP吧这题 
    //与其一个一个排序,不如看做是好几个数一个一个往里插入
    //dp[i][j]:序列里有i个数,且数列里已经有j个'<'的方案数 
    //最后答案就是:dp[n][k] 
    int main()
    {
        //freopen("num.in","r",stdin);
        //freopen("num.out","w",stdout);
        n=read();k=read();
        for(int i=1;i<n;i++)      //当前序列里有 
        {
            dp[i][0]=1;           //边界情况,只能从大到小排好
            for(int j=0;j<i;j++)  //枚举序列里有多少个小于号 
            {
                dp[i+1][j]=(dp[i+1][j]+dp[i][j]*(j+1)%mod)%mod;   //有(j+1)个空插入后使得原序列的小于号个数不变
                dp[i+1][j+1]=(dp[i+1][j+1]+dp[i][j]*(i-j)%mod)%mod; //i个数,只有i-1个符号,其中小于号有j个,大于号就有i-1-j,再加上最右边还有一个空 
            } 
        }
        printf("%d",dp[n][k]%mod);
        return 0;
    } 
  • 相关阅读:
    OC中数组的使用方法
    fuel Explain
    OpenStack images
    linux dd实现磁盘完整全盘镜像备份backup,恢复recover(restore)
    linux大事件集
    Ruiy classicsQuotations
    OpenSuSE zypper OpenStack Icehouse repoAdd
    OpenSuSE zypper repo及Desktop媒体播放器设置 for OpenSuSE12.
    OpenSuSE查看指定软件包是否安装(OpenSuSE使用RPM作为默认的软件包维护管理工具)
    OpenStack开启sshd
  • 原文地址:https://www.cnblogs.com/xcg123/p/11166662.html
Copyright © 2020-2023  润新知