【每日一题】Problem 189A. Cut Ribbon

原题

解决思路

完全背包问题
用 dp[i][j] 表示到 i 个切分长度,总长度为 j 时,可以裁剪的最大长度
5 5 3 2 为例

image

说明:

  1. 以 "0" 元素开始的那行,只是为了特殊情况,例如后面防止 \(i-1\) 越界,以及其他可能要设置初始值的时候,需要这行,因此需要保留
  2. 非 "0" 元素的每行中所包含的 "0", 表示当前情况下的裁剪不满足要求,例如 \([3,4]\),一段为 \(3\),剩下一段为 \(1\),不满足要求
  3. 非 "0" 元素的每行中所包含的非 "0" 值,代表 \([i,j]\) 下能裁剪的最大段数
    • 例如 \([2, 5]\) 可以剪成 \([2, 3] + 1 = 1 + 1 = 2\)
#include <bits/stdc++.h>

int main() {
    int n; std::cin >> n;
    std::vector<int> len(3, 0);
    for (int i = 0; i < 3; ++i) std::cin >> len[i];

    std::vector<std::vector<int>> dp(4, std::vector<int>(n + 1, 0));
    for (int i = 1; i < 4; ++i) {
        for (int j = 1; j <= n; ++j) {
            // 当总长度小于目标裁剪长度时,此时无法裁剪出长度 i
            // 因此,段数以前一个元素 i-1 在总长度为 j 时可以裁剪的段数为准
            if (j < len[i - 1]) dp[i][j] = dp[i - 1][j];
            else {
                // 需要判断减掉当前长度时,上下的长度能否裁剪出满足要求的长度
                // 若不能,则段数以前一个元素 i-1 在总长度为 j 时可以裁剪的段数为准
                // 需要注意的是,剩余长度为 dp[i][0] 时是一个特例
                if (j != len[i - 1] && dp[i][j - len[i - 1]] == 0) dp[i][j] = dp[i - 1][j];
                else dp[i][j] = std::max(dp[i - 1][j], dp[i][j - len[i - 1]] + 1);
            }
        }
    }
    std::cout << dp[3][n] << std::endl;
    return 0;
}
更好的解

特别地,对于 dp[i][j] = std::max(dp[i - 1][j], dp[i][j - len[i - 1]] + 1)

  1. 在二维数组中,dp[i - 1][j]dp[i][j] 处于同一列;
  2. 如果把这两个元素放进一维数组 newdp,那么可以把 dp[i-1][j] 看作是更新为 dp[i][j] 前的旧值
    举个例子:
    image
    一维数组 newdp 当前值为红框圈出这行的值,当 for 循环进入 i == 3 时,以 \([2,3]\) 为例
    image
    此时 newdp[j] = 1,即dp[i][j] = 1,而dp[i-1][j] = 1([3,3]),对应上一轮的 newdp[j] = ${[3,3]}
    dp[i][j - len[i - 1]] + 1newdp[j-len[i-1]],由于 \(j\) 在循环中是升序,因此该值早于newdp[j]更新
    以上,使用滚动数组完成了二维数组向一维数组的转化
  • 以下简单地将二维数组替换成一维数组
#include <bits/stdc++.h>

int main() {
    int n; std::cin >> n;
    std::vector<int> len(3, 0);
    for (int i = 0; i < 3; ++i) std::cin >> len[i];

    std::vector<int> dp(n + 1, 0);
    for (int i = 1; i < 4; ++i) {
        for (int j = 1; j <= n; ++j) {
            if (j < len[i - 1]) continue;
            else {
                if (j != len[i - 1] && dp[j - len[i - 1]] == 0) continue;
                else dp[j] = std::max(dp[j], dp[j - len[i - 1]] + 1);
            }
        }
    }
    std::cout << dp[n] << std::endl;
    return 0;
}
  • 合并条件分支
#include <bits/stdc++.h>

int main() {
    int n;
    std::cin >> n;
    std::vector<int> len(3, 0);
    for (int i = 0; i < 3; ++i) std::cin >> len[i];

    std::vector<int> dp(n + 1, 0);
    for (int i = 1; i < 4; ++i) {
        for (int j = 1; j <= n; ++j) {
            if (j >= len[i - 1] && (j == len[i - 1] || dp[j - len[i - 1]] != 0))
                dp[j] = std::max(dp[j], dp[j - len[i - 1]] + 1);
        }
    }
    std::cout << dp[n] << std::endl;
    return 0;
}
感慨

动态规划真的绕脑子啊,还是回力扣做了好几道动态规划,又学了一下 0-1/完全背包的二维实现、滚动数组实现,积攒了好几天的题,才终于能补上了。。

posted @ 2023-06-16 23:51  HelloEricy  阅读(5)  评论(0编辑  收藏  举报