【算法笔记】01背包问题+完全背包问题(2022-2-3)

今天看了四五个小时背包问题才算有点思路,现在记录下来理解过程以防遗忘。

01背包问题

学习01背包问题首先要知道动态规划是什么。

动态规划:动态规划算法通常基于一个递推公式及一个或多个初始状态。 当前子问题的解将由上一次子问题的解推出。使用动态规划来解题只需要多项式时间复杂度, 因此它比回溯法、暴力法等要快许多。

01背包问题是典型的动态规划,所谓01,是表示在一系列有限对象中进行选择,“0”代表不选,1代表选择。

题目:有n件物品,每件物品的体积为v[i],价值为c[i]。现有一个容量为V的背包,问如何选取物品放入背包,使得背包内物品的总价值最大。其中每种物品都只有一件。

思路:

  1. 限制条件是体积V,因此dp[i]中的关键字“i”是体积,即dp[i] = 体积为i时的最优解(最大价值);

  1. 每一轮应该考虑的是“0~i个物品”可供选择时,体积从0~V的所有最优解;

  1. 以上一轮得出的最优解为基础来计算“0~k+1个物品”可供选择时的最优解,重复bc直至i>n;

  1. 此时得到“0~n个物品”可供选择时,且体积为1~V的所有最优解,dp[V]即为所求。

代码:

int main() {
    int n, V;
    int v[100], c[100];
    scanf("%d%d", &n, &V);
    for (int i = 1; i <= n; i++) {
        cin >> v[i];
    }
    for (int i = 1; i <= n; i++) {
        cin >> c[i];
    }
    int dp[100];
    fill(dp, dp + 100, 0);
    for (int i = 1; i <= n; i++) {
        for (int j = V; j >= v[i]; j--) {
            dp[j] = max(dp[j], dp[j - v[i]] + c[i]);
        }
    }
    int ans = 0;
    for (int i = 1; i <= V; i++) {
        if (ans < dp[i])
            ans = dp[i];
    }
    cout << ans;
}

思考:

  1. 为什么明明考虑了1~n,1~V的所有情况,用的dp[]却是一维数组?

因为1~n考虑的是对不同物品的选择,而每次只添加一个物品,并且添加一个物品选项“i”后的结果只可能有两种:①不选择,则dp[j]不变;②选择,则dp[j]要留出v[i]的空间来,同时价值量增加c[i];

最优解为二者较大值,由此可得递推式(状态转移方程):

dp[j] = max( dp[j] , dp[ j-v[i] ] + c[i] ) ;

由此可见,下一轮的结果只与其自己的位置“i”以及左边位置“i-v[k]”有关,故只需要从右向左的进行数据更新,便可以不用保存选择更少物品时的结果(已经不会再用上了)。

  1. 为什么对体积的更新只需要从右向左到v[i] ?左边的不更新吗?

因为每一轮是在更新对“物品i的添加”是否会有更优解,而 j<v[i] 时,i都放不进来了(体积不够用),也就无需判断了,事实上也不能判断,数组会越界了。

完全背包问题

01背包强调01,也就是选和不选,完全背包则没有01的概念,并不是因为它没有01,而是因为它不仅有01,还有2345... 也就是说每个物品没有“最多只能取一个”的限制。

【问题描述】这是一个古老而又经典的问题。用给定的几种钱币凑成某个钱数,一般而言有多种方式。例如:给定了 6 种钱币面值为 2、5、10、20、50、100,用来凑 15 元,可以用 5 个 2 元、1个 5 元,或者 3 个 5 元,或者 1 个 5 元、1个 10 元,等等。显然,最少需要 2 个钱币才能凑成 15 元。

你的任务就是,给定若干个互不相同的钱币面值,编程计算,最少需要多少个钱币才能凑成某个给出的钱数。

【输入形式】输入可以有多个测试用例。每个测试用例的第一行是待凑的钱数值 M(1 <= M<= 2000,整数),接着的一行中,第一个整数 K(1 <= K <= 10)表示币种个数,随后是 K个互不相同的钱币面值 Ki(1 <= Ki <= 1000)。输入 M=0 时结束。

【输出形式】每个测试用例输出一行,即凑成钱数值 M 最少需要的钱币个数。如果凑钱失败,输出“Impossible”。你可以假设,每种待凑钱币的数量是无限多的。

【样例输入】

15

6 2 5 10 20 50 100

1

1 2

0

【样例输出】

2

Impossible

原文链接:https://blog.csdn.net/www_helloworld_com/article/details/83152190

完全背包和01背包一样,将最终要输出的答案记为dp[],而限制条件(钱的数值)作为关键字“i”,即dp[i] 表示钱的总额为i时的最少钱币数量。

初始条件:

①dp[i] 是求最小值,因此初始化时令其为无穷大的值(如99999),dp[0] = 0(M=0时不用金币即可满足);

② i 从0~M;

每轮更新:

对于每一轮的最优解更新时,是在原有的1~ i-1 种钱币组成的最优解上纳入一种新的钱币“i”,并可以纳入无数个“i”号钱币。对于每种钱币只有两种选择:

①不放入这种钱币,则dp[ i ] = dp [ i ] 不变;

②放入一个这种钱币,则dp[ i ] = dp[ i-coins[ i ] ] + 1 ; (为钱币“i”腾出位置后的最小钱币数dp[ i - coins[i] ] 加上1个新钱币“i”);

代码:

int main() {
    int K, M; //K kind, M money
    scanf("%d%d", &M, &K);
    int coins[1000], dp[1000];
    fill(dp, dp + M + 1, 99999);
    dp[0] = 0;
    for (int i = 1; i < K + 1; i++) {
        cin >> coins[i];
    }
    for (int i = 1; i <= K; i++) {
        for (int j = coins[i]; j <= M; j++) {
            dp[j] = min(dp[j], dp[j - coins[i]] + 1);
        }
    }
    if (dp[M] == 99999) {
        cout << "impossible" << endl;;
    }
    else {
        cout << dp[M] << endl;
    }
}

思考:

  1. 为什么只用考虑dp[ j-v[i] ] + c [i] ? 不是说好的可以选好几个吗?dp[ j - 2v[i] ] + 2 c[i] 怎么就不考虑了呢?

这就是动态规划的魅力,因为如果有足够的空间能选择两个 i 物品,且两个 i 物品的选择是更优解,则dp[ j-v[i] ] 中已经包含了这个额外的 i ,所以无需额外考虑(因为完全背包是从左向右进行更新的,由于dp[ j - 2v[i] ] 在dp[ j-v[i] ] 的左边,所以已经考虑过了选第二个的情况)。

换一种说法进行反证:

如果dp[ j - 2v[i] ] +2 < dp[ j-v[i] ] + 1,

说明 dp[ j - 2v[i] ] +1 < dp[ j-v[i] ]

那么在j = j-v[i] 的时候就应该更新,故不可能出现上述情况

  1. 怎么选取外层循环和内层循环的变量及关键字?

外层变量的关键字“i”应该是可供选择的选项,在01背包的例子中,供选择的是“不同的物品”,在完全背包的例子中,供选择的是“不同的金币”,也就是说每次循环添加一种变量进行考虑。

外层变量的值“dp[i]”应该是所求目标值,在01背包的例子中,价值总量是需要求出的目标值,在完全背包的例子中,金币个数是需要求出的目标值。这个很好理解,因为最后返回的就是dp中的值。

内层变量关键字“j”应该是被限制的那个变量,在01背包的例子中,体积是被限制的变量,在完全背包的例子中,金币总额是被限制的变量。也就是说,在同一次循环中利用已有的变量(物品或金币),求出所有限制条件下的最优解。

以上内容参考了一位大佬的博客,小白可能文不达意,此处留下大佬原文:最少钱币数(凑硬币)详解-2-动态规划算法(初窥)-编程练习题(100)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值