• 《挑战程序设计竞赛》2.3 动态规划-优化递推 POJ1742 3046 3181


    POJ1742

    http://poj.org/problem?id=1742

    题意

    有n种面额的硬币,面额个数分别为Ai、Ci,求最多能搭配出几种不超过m的金额?

    思路

    据说这是传说中的男人8题呢,对时间和空间复杂度要求都挺高的。
    朴素DP三重循环比较容易想到,但显而易见会TLE。
    这里由于dp数组记录的是一个bool值(是否能搭配出某金额),记录的信息较少,因而存在浪费。优化思路是dp[i][j]记录用前i种数加和得到j时第i种数最多能剩余多少个(不能加和得到的情况下为-1)。但二维dp数组的空间复杂度仍然没有降下来,这时候会MLE。
    改成一维数组几个AC。
    关于思路更详细的说明见《挑战程序设计竞赛》第62-64页。
    另外可参见另一篇博客,对于这道题解释的非常清楚:POJ1742详解

    代码

    Source Code
    
    Problem: 1742       User: liangrx06
    Memory: 564K        Time: 1907MS
    Language: C++       Result: Accepted
    Source Code
    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    using namespace std;
    
    const int N = 100;
    const int M = 100000;
    
    int main(void)
    {
        int n, m;
        int a[N], c[N];
        int dp[M+1];
    
        while (scanf("%d%d", &n, &m) != EOF) {
            if (!n && !m) break;
            for (int i = 0; i < n; i ++)
                scanf("%d", &a[i]);
            for (int i = 0; i < n; i ++)
                scanf("%d", &c[i]);
    
            fill(dp, dp+m+1, -1);
            dp[0] = 0;
            for (int i = 0; i < n; i ++) {
                for (int j = 0; j <= m; j ++) {
                    if (dp[j] >= 0)
                        dp[j] = c[i];
                    else if (j < a[i] || dp[j-a[i]] <= 0)
                        dp[j] = -1;
                    else
                        dp[j] = dp[j-a[i]] - 1;
                }
            }
    
            int ans = 0;
            for (int i = 1; i <= m; i ++) {
                if (dp[i] >= 0) ans++;
            }
            printf("%d
    ", ans);
        }
    
        return 0;
    }

    POJ3046

    http://poj.org/problem?id=3046

    题意

    有A只蚂蚁,来自T个家族。同一个家族的蚂蚁长得一样,但是不同家族的蚂蚁牙齿颜色不同。任取n只蚂蚁(S<=n<=B),求能组成几种集合?
    还可以理解为:
    给出T种数字。每种各有N[i]个,然后用这些数字构成一些序列, 问x长度到y长度的序列有多少种?

    思路

    动态规划题,dp[i][j] 表示前i种数字构成长度为j的序列有多少种。递推关系为:
    dp[i][j] = sigma(dp[i - 1][j - k]) k的范围是0~N[i]
    注意到这里的sigma(dp[i - 1][j - k]) 可以用部分和算一下。
    然后因为总共的数字个数可能有10W个,有1000种数组,会MLE。所以需要开滚动数组来搞。
    复杂度的话 是 O(sigma(num[i] * (T + 1 - i))),最坏是1亿。

    代码

    Source Code
    
    Problem: 3046       User: liangrx06
    Memory: 1028K       Time: 125MS
    Language: C++       Result: Accepted
    Source Code
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    
    const int T = 1000;
    const int A = T*100;
    const int MOD = 1000000;
    
    int main(void)
    {
        int t, a, s, b;
        int fam[T];
    
        cin >> t >> a >> s >> b;
        memset(fam, 0, sizeof(fam));
        int num;
        for (int i = 0; i < a; i ++) {
            scanf("%d", &num);
            fam[num-1] ++;
        }
    
        int dp[2][A+1];
        memset(dp, 0, sizeof(dp));
        for (int j = 0; j <= b; j ++) {
            if (j <= fam[0]) dp[0][j] = 1;
        }
        for (int i = 1; i < t; i ++) {
            for (int j = 0; j <= b; j ++) {
                dp[i&1][j] = 0;
                for (int k = 0; k <= j && k <= fam[i]; k ++) {
                    dp[i&1][j] += dp[(i-1)&1][j-k];
                }
                dp[i&1][j] %= MOD;
            }
        }
        int ans = 0;
        for (int j = s; j <= b; j ++)
            ans += dp[(t-1)&1][j];
        printf("%d
    ", ans % MOD);
    
        return 0;
    }

    POJ3181

    http://poj.org/problem?id=3181

    题意

    输入n,和k,问将n用1到k这k个数字进行拆分,有多少种拆分方法。例如:
    n=5,k=3 则有n=3+2,n=3+1+1,n=2+1+1+1,n=2+2+1,n=1+1+1+1+1这5种拆分方法

    思路

    很明显是完全背包问题,假如用dp[i][j]表示考虑到用数1-i拼接成数字j的拼接方法,可以得到状态转移方程如下:
    dp[i][j]=sigma(dp[i-1][j-k*i])(k>=0,且j-k*i>=0)
    这个题的答案比较大,超过了long long的表示范围,但其实答案的范围可以大致试出来,两个long long拼起来足够表示。
    另外二维数组显然是空间浪费的,用滚动数组处理可以将内存空间降维。甚至可以用一维数组表示。
    最后,累加实际上是不必要的,还有更进一步的优化思路:
    其实可以转到dp[i][j]的状态有两种,一种是dp[i-1][j]就是不用i这个数字拼接成j这个数字的方法数,另一种是dp[i][j-i]就是用了i这个数字拼接的到j-i的方法数。
    那么状态转移方程就可以写成:
    dp[i][j]=dp[i-1][j]+dp[i][j-i]
    做本题的过程中参考了这篇博文:POJ 3181 Dollar Dayz (完全背包)

    代码1(累加DP)

    Source Code
    
    Problem: 3181       User: liangrx06
    Memory: 264K        Time: 47MS
    Language: C++       Result: Accepted
    Source Code
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    
    const int K = 100;
    const int N = 1000;
    
    int main(void)
    {
        int n, k;
    
        cin >> n >> k;
    
        long long INF = 1;
        for (int i = 0; i < 18; i++)
            INF *= 10;
    
        long long dp[2][N+1][2];
        memset(dp, 0, sizeof(dp));
        dp[0][0][0] = 1;
        dp[0][0][1] = 0;
        for (int i = 1; i <= k; i ++) {
            for (int j = 0; j <= n; j ++) {
                dp[i&1][j][0] = dp[i&1][j][1] = 0;
                for (int k = 0; k <= j; k += i) {
                    dp[i&1][j][0] += dp[(i-1)&1][j-k][0];
                    dp[i&1][j][1] += dp[(i-1)&1][j-k][1];
                    if (dp[i&1][j][0] >= INF) {
                        dp[i&1][j][0] -= INF;
                        dp[i&1][j][1] += 1;
                    }
                }
            }
        }
        if (dp[k&1][n][1])
            printf("%lld", dp[k&1][n][1]);
        printf("%lld
    ", dp[k&1][n][0]);
    
        return 0;
    }

    代码2(优化DP,时间复杂度降维)

    Source Code
    
    Problem: 3181       User: liangrx06
    Memory: 264K        Time: 16MS
    Language: C++       Result: Accepted
    Source Code
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    
    const int K = 100;
    const int N = 1000;
    
    int main(void)
    {
        int n, k;
    
        cin >> n >> k;
    
        long long INF = 1;
        for (int i = 0; i < 18; i++)
            INF *= 10;
    
        long long dp[2][N+1][2];
        memset(dp, 0, sizeof(dp));
        dp[0][0][0] = 1;
        dp[0][0][1] = 0;
        for (int i = 1; i <= k; i ++) {
            for (int j = 0; j <= n; j ++) {
                dp[i&1][j][0] = dp[(i-1)&1][j][0] + ((j-i >= 0) ? dp[i&1][j-i][0] : 0);
                dp[i&1][j][1] = dp[(i-1)&1][j][1] + ((j-i >= 0) ? dp[i&1][j-i][1] : 0);
                if (dp[i&1][j][0] >= INF) {
                    dp[i&1][j][0] -= INF;
                    dp[i&1][j][1] += 1;
                }
            }
        }
        if (dp[k&1][n][1])
            printf("%lld", dp[k&1][n][1]);
        printf("%lld
    ", dp[k&1][n][0]);
    
        return 0;
    }
  • 相关阅读:
    RedHat 7.0及CentOS 7.0禁止Ping的三种方法
    修改WordPress后台默认登陆地址提高网站安全性
    解决WordPress用户名密码都正确但点击登陆就清空密码的问题
    Windows上使用Git托管代码到Coding
    使用Coding Pages托管网站
    Windows上设置Mozilla Thunderbird邮件客户端后台运行
    在VirtualBox中安装BlackArch Linux
    关于XML学习
    软件工程课程设计团队项目总结与项目报告
    详细设计
  • 原文地址:https://www.cnblogs.com/liangrx06/p/5083758.html
Copyright © 2020-2023  润新知