• 2016级算法第三次上机-G.Winter is coming


    904 Winter is coming

    思路

    难题。首先简化问题, (n) 个0与 (m) 个1排成一列,连续的0不能超过x个,连续的1不能超过y个,求排列方法数。

    显然会想到这是动态规划。最快想到的方法是 (dp[i][j][x][y]) 表示已经有i个北境兵j个野人参与排列,且末尾有x个连续北境士兵或y个连续野人士兵的方案数。这方法显然是正确的,但是光是 (dp[200][200][10][10]) 数组已经十分接近本题内存限制了,保证MLE。状态转移方法是大模拟,四层for循环,每次增加一个人放在最后,讨论各种情况。具体代码可见MLE参考代码,比较好理解。

    不过这个方法已经和答案很接近了,只需要稍微优化一下。可以发现第三维和第四维很多空闲的空间被浪费了,我们没有必要用两个维度来分别记录有几个0或1,可以把第四维变成一个标志位,0代表北境军,1代表野人军,而第三维记录最后一段连续的人数,这样空间变成原来的1/6,算是简单的优化了一下,思想并没有变。具体可见优化代码,在此感谢孟尧提供。

    本题还可以继续优化,换种方式,dp[i][j][k]:已经有i个北境兵j个野人参与排列,第三维k是标志位(0代表北境军,1代表野人军)的排列方法数。状态转移方程变为:: (dp[i][j][0]=∑(dp[i-k][j][1])%MOD) ;其中 (k∈[1,min(i,x)]) 。同理, (dp[i][j][1]=∑(dp[i][j-k][0])%MOD) ;其中 (k∈[1,min(j,y)]) 。相信你很快就能看懂,这里用 (dp[i-k][j][1]) 来代表最后有k个0,相当于同时把三四维合并了,巧妙至极。具体可见最优参考代码。

    分析

    本题不卡时间,卡的是内存。目的是让大家在解决问题的时候有优化的思想(实际的目的是把它从中等题变成难题)。

    DP只能意会,不可言传。大家在做DP题的时候一定要理清思路,一般是先不管空间,毕竟以空间换时间,大多数题都是先卡时间再卡空间的。

    以本题为例粗略讲解一下DP,以后不会再讲。记住DP具备的两个要素:最优子结构和子问题重叠,见《算法导论》225页。本问题明显备最优子结构,最少的排列数是由多个较短一点的最少排列数组成。DP的多层循环也是有规律的,因为子问题的重叠,你得先把子问题算出来,才能计算更深层的。这里i和j从小到大地计算,保证所加上的都是已经计算过的,才不会出现问题,如果这题加一个dp[i+1][j][1],那明显不对了,因为这个还没有计算过。状态转移方程有时候很微妙,需要一番数学推理。

    最优参考代码

    //
    // Created by AlvinZH on 2017/10/24.
    // Copyright (c) AlvinZH. All rights reserved.
    //
    
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #define MOD 1000007
    using namespace std;
    
    int n, m, x, y;
    int dp[205][205][2];
    
    int main()
    {
        while(~scanf("%d %d %d %d", &n, &m, &x, &y))
        {
            memset(dp, 0, sizeof(dp));
            for (int i = 0; i <= x; ++i)
                dp[i][0][0] = 1;
            for (int i = 0; i <= y; ++i)
                dp[0][i][1] = 1;
    
            for (int i = 1; i <= n; ++i) {
                for (int j = 1; j <= m; ++j) {
                    for (int k = 1; k <= min(i,x); ++k)
                        dp[i][j][0] = (dp[i][j][0] + dp[i-k][j][1]) % MOD;
    
                    for (int k = 1; k <= min(j,y); ++k)
                        dp[i][j][1] = (dp[i][j][1] + dp[i][j-k][0]) % MOD;
                }
            }
    
            printf("%d
    ", (dp[n][m][0] + dp[n][m][1]) % MOD);
        }
    }
    
    /* 把第三四维合并,因为我们只要在状态转移的时候保证最后连续一段不超过x或y就好了,第三维用来记录最后连续的是0还是1就好了。
     * dp[i][j][k]:已经有i个北境兵j个野人参与排列,k为标志位(0代表北境军,1代表野人军)的排列方法数。
     */
    

    优化参考代码

    /*
     Author: 孟爻(12593)
     Result: AC	Submission_id: 403884
     Created at: Sun Nov 12 2017 23:05:10 GMT+0800 (CST)
     Problem: 904	Time: 73	Memory: 11232
    */
    
    #include <cstdio>
    #include <cstring>
    
    long f[205][205][15][2];
    long M = 1000007;
    long n,m,x,y;
    
    int main() {
        while(~scanf("%ld%ld%ld%ld",&n,&m,&x,&y))
        {
            memset(f,0,sizeof(f));
            f[0][0][0][0]=1;
            for(long i=0; i<=n; i++) {
                for(long j=0; j<=m; j++) {
                    if(i) {
                        for(long k=1; k<=x; k++)
                            f[i][j][k][0]=(f[i][j][k][0]+f[i-1][j][k-1][0])%M;
                        for(long k=0; k<=y; k++)
                            f[i][j][1][0]=(f[i][j][1][0]+f[i-1][j][k][1])%M;
                    }
                    if(j) {
                        for(long k=1; k<=y; k++)
                            f[i][j][k][1]=(f[i][j][k][1]+f[i][j-1][k-1][1])%M;
                        for(long k=0; k<=x; k++)
                            f[i][j][1][1]=(f[i][j][1][1]+f[i][j-1][k][0])%M;
                    }
                }
            }
            long ans=0;
            for(long k=1; k<=x; k++)
                ans=(ans+f[n][m][k][0])%M;
            for(long k=1; k<=y; k++)
                ans=(ans+f[n][m][k][1])%M;
    
            printf("%ld
    ",ans);
        }
    }
    

    MLE代码

    //
    // Created by AlvinZH on 2017/10/24.
    // Copyright (c) AlvinZH. All rights reserved.
    //
    
    #include <cstdio>
    #include <cstring>
    #define MOD 1000007
    
    int n, m, x, y;
    int dp[205][205][12][12];
    
    int main()
    {
        while(~scanf("%d %d %d %d", &n, &m, &x, &y))
        {
            memset(dp, 0, sizeof(dp));
            dp[0][0][0][0] = 1;
            for (int i = 0; i <= n; ++i) {
                for (int j = 0; j <= m; ++j) {
                    for (int k = 0; k <= x; ++k) {
                        for (int l = 0; l <= y; ++l) {
                            if(dp[i][j][k][l] == 0) continue;
                            if(i != n && k != x)//末尾是北境兵
                            {
                                dp[i+1][j][k+1][0] += dp[i][j][k][l];
                                dp[i+1][j][k+1][0] %= MOD;
                            }
                            if(j != m && l != y)//末尾是野人兵
                            {
                                dp[i][j+1][0][l+1] += dp[i][j][k][l];
                                dp[i][j+1][0][l+1] %= MOD;
                            }
                        }
                    }
                }
            }
            int ans = 0;
            for (int i = 1; i <= x; ++i)
                ans = (ans + dp[n][m][i][0]) % MOD;
            for (int i = 1; i <= y; ++i)
                ans = (ans + dp[n][m][0][i]) % MOD;
    
            printf("%d
    ", ans);
        }
    }
    
    /*
     * dp[i][j][x][y]表示已经有i个北境兵j个野人参与排列,且末尾有x个连续北境士兵或y个连续野人士兵的方案数。
     */
    
  • 相关阅读:
    Vpython简单例子
    我在读的书:《ACM图灵奖:19662006(第三版)计算机发展史的缩影》
    可惜啊,没见到姚期智~~
    The Sounds of Music 观后感
    终于在博客园申请开通博客了
    【引用】Python open读写文件实现脚本
    在Python中使用中文
    Discovery:深入理解计算机系统 (Ver.2) 中文版
    [导入]一个都不能少:全面认识IE插件
    [导入]午间心情
  • 原文地址:https://www.cnblogs.com/AlvinZH/p/7977917.html
Copyright © 2020-2023  润新知