如何使用动态规划自顶向下方法解决这个 p‌r‌o‌b‌l‌e‌m?

Posted

技术标签:

【中文标题】如何使用动态规划自顶向下方法解决这个 p‌r‌o‌b‌l‌e‌m?【英文标题】:How do I solve this p‌r‌o‌b‌l‌e‌m using Dynamic Programming Top Down approach? 【发布时间】:2015-05-17 15:23:26 【问题描述】:

我正在尝试解决 Codeforces 的问题 (http://codeforces.com/problemset/problem/189/A) 这是问题陈述:

Polycarpus 有一条丝带,它的长度是 n。他希望以满足以下两个条件的方式剪彩:

切割后,每条丝带的长度应为 a、b 或 c。 切割后的色带片数应为最大。 帮助 Polycarpus 并找到所需的丝带片数 切割。

输入 第一行包含四个空格分隔的整数 n、a、b 和 c (1 ≤ n, a, b, c ≤ 4000) — 相应的原始色带长度和切割后色带片的可接受长度。数字a、b和c可以重合。

输出 打印单个数字 — 最大可能的色带片数。保证至少存在一个正确的剪彩。

示例输入

5 5 3 2

样本输出

2

我尝试使用动态编程(自上而下的方法)来解决这个问题。但我无法得到正确的答案。递归函数可能有问题。这是我的代码:

#include<bits/stdc++.h>
using namespace std;

int n,s;
int a[3];
int val,m=-1;
int dp(int n)

    if(n==0)
        return 0;
    for(int i=0;i<3;i++)
    
        if(n>=a[i])
        
            val=1+dp(n-a[i]);
        
    
    if(val>m)
        m=val;
    return m;


int main()

    scanf("%d %d %d %d",&n,&a[0],&a[1],&a[2]);
    cout<<dp(n)<<endl;
    return 0;

上述方法有什么问题?

【问题讨论】:

首先,您通常不应该包含&lt;bits/*&gt; 头文件,尤其是&lt;bits/stdc++.h,除非您确实需要该文件包含的所有头文件。 包含您需要的头文件,在您的情况下为&lt;cstdio&gt;&lt;iostream&gt;。在 C++ 程序中使用scanf 有什么原因吗? @JoachimPileborg 我知道在 C++ 代码中使用 或 scanf 不是一个好方法。但是这些策略被用于编程竞赛。这是我的编程竞赛问题代码。 别人有坏习惯,不代表你就应该有。如果您想成为一名优秀的程序员,请从一开始就使用良好的编程技术和良好的习惯。并且不要参加编程竞赛,它们不会真正让你成为一名优秀的程序员,除非你认为混淆和未注释的代码很好。学习好的编程技术、基本和高级的数据结构和算法,最重要的是如何编写好的、可读的和可维护的代码,然后如果你还想参加这些竞赛。 我认为你不应该在这里使用 dp,对于像 10000 1 2 3 这样的东西,你的方法可能需要几个小时来计算。 如果你把它分解为“5 5 3 2”的输入,你在某个时刻得到 val = 1,在你完成 n = 5, n = 0 ( 1 + dp (0) ), val = 1。然后当您执行 5 - 2 - 2 时,您还剩 1 个,然后您的所有测试 if(n >= a[i]) 失败,您返回 val,即 1,然后您将 1 添加到 1,当你有 5 - 2 - 2 并且你做 val = 1 + dp(1),到目前为止你有 2,然后你把那个 2 添加到另一个 1,当你做 val = 1 + dp( 3),所以你总共得到 3。对于“7 5 5 2”的输入,你从 7 中减去 2 3 次,最后你得到 1,它仍然算数。 【参考方案1】:

有几个问题:

错误的搜索

在你的台词中

for(int i=0;i<3;i++)

    if(n>=a[i])
    
        val=1+dp(n-a[i]);
    

if(val>m)
    m=val;

您应该检查针对i 的不同选择获得的不同vals 的最大值。

错误终止

如果长度不为 0 且无法切割丝带,则应返回类似负无穷大的值。您当前返回的 m 最初为 -1(稍后会详细介绍)。这是错误的,对于长色带,基本上可以确保您只选择 a、b 和 c 中的最小值。

全局变量的使用

一些全局变量,例如 m 被初始化一次,但被递归修改。这不仅仅是“糟糕的”编程习惯——它没有做你想做的事。

不可重复使用

通过无条件调用递归,而不是重用以前的调用,你的运行时间会不必要地长。

【讨论】:

我使用 (n>=a[i]) 来防止函数 dp 具有负参数。因为 dp(一些负数) 没有用。这是我的假设。我是不是哪里错了? @ScottWu 确实,根据问题的陈述,cut 总是存在的,但是您仍然有列表中的第一个和第三个问题,并且可能还有时间限制【参考方案2】:
int main() 
 int n, a, b, c;
 scanf("%d %d %d %d", &n, &cuts[0], &cuts[1], &cuts[2]);
 sort(cuts, cuts + 3);
 for (int i = 0; i <= n; i++) 
    max_cuts[i] = INT_MIN;
 

 max_cuts[0] = 0;
 max_cuts[cuts[0]] = 1;
 max_cuts[cuts[1]] = 1;
 max_cuts[cuts[2]] = 1;

 for (int i = 1; i <= n; i++) 
    for (int j = 0; j < 3; j++) 
        if (cuts[j] > i) break;
        max_cuts[i] = max(max_cuts[i - cuts[j]] + 1, max_cuts[i]);
    
 
 printf("%d\n", max_cuts[n]);
 return 0;

【讨论】:

【参考方案3】:

@Ami Tavory 正确地提出了递归方法的问题。也许我下面的解决方案可以帮助您更好地理解如何形成状态和检查边界:

    int main()
    
            int n, a, b, c;
            cin >> n >> a >> b >> c; 

            const int l = n + 1;
            int sum[l];
            fill(sum, sum+l, INT_MIN);
            sum[0] = 0;

            for(int i=1; i<=n; i++)
            
                    if(i - a >= 0)
                    
                            sum[i] = sum[i-a] + 1;
                    
                    if(i - b >= 0 && sum[i-b] + 1 > sum[i])
                    
                            sum[i] = sum[i-b] + 1;
                    
                    if(i - c >= 0 && sum[i-c] + 1 > sum[i])
                    
                            sum[i] = sum[i-c] + 1;
                    

            
            cout << sum[n] << endl;
            return 0;
    

仅在每个 sum[i] 处,我们都在最大化削减的数量。因此,在 sum[i] 处,我们存储了 max(sum[i-a]+1, sum[i-b]+1, sum[i-c]+1)。 除此之外,只有绑定检查。

【讨论】:

【参考方案4】:

你可以通过自上而下的方法解决这个问题。一个 dp 问题总是检查所有可能的情况,然后给我们最佳解决方案。所以这里是代码

#include<bits/stdc++.h>
using namespace std;
int a,b,c;
int DP[4001];
int solve(int n)
if(n == 0)return 0;
if(n<0) return INT_MIN;
if(DP[n] != -1)return DP[n];
else
   DP[n] = max(1+solve(n-a),max(1+solve(n-b),1+solve(n-c)));
return DP[n];


int main()
int n,x;
cin>>n>>a>>b>>c;
for(int i = 0;i<4001;++i)
DP[i] = -1;

x = solve(n);
cout<<x;

【讨论】:

以上是关于如何使用动态规划自顶向下方法解决这个 p‌r‌o‌b‌l‌e‌m?的主要内容,如果未能解决你的问题,请参考以下文章

坐标型动态规划总结,再附两个算法

最长回文子串自顶向下动态规划

python动态演示动态规划解决矩阵连乘

动态规划算法学习

动态规划-自底向上的 0-1 背包问题

#动态规划 LeetCode 120 三角形最小路径和