【机试备考】0-1背包问题及方案总数

前言

今天做到了一道涉及动态规划的问题,自己不会解,看题解都说是0-1背包问题的变体,这个问题刚开始就没太明白,后来每次遇到也没有很好解决,所以今天下定决心把它学会
 

0-1背包问题

有 N 件物品和一个容量为 V 的背包。放入第 i 件物品占用的体积是 C[i] ,得到的价值是 W[i] 。求解将哪些物品装入背包可使价值总和最大。求出最大总价值。

代码如下:注释相对解释的比较清楚

  • 二维数组版
// V是背包容量,C是每件物品的体积,W是每件物品的价值量
public static int zeroOnePack(int V, int[] C, int[] W) {
    
     
    // 防止无效输入
    if ((V <= 0) || (C.length != W.length)) {
    
    
        return 0;
    }

    int n = C.length;//物品个数

    // dp[i][j]: 对于下标为 0~i 的物品,背包容量为 j 时的最大价值
    int[][] dp = new int[n + 1][V + 1];

    // 背包空的情况下,价值为 0
    dp[0][0] = 0;

    for (int i = 1; i <= n; ++i) {
    
    
        for (int j = 1; j <= V; ++j) {
    
    
            // 同样的背包容量,不选物品 i 的话,当前价值就是取到前一个物品的最大价值,也就是 dp[i - 1][j]
            dp[i][j] = dp[i - 1][j];
            // 如果选取i,则背包容量减少C[i],价值增加W[i]
            // 如果选择物品 i 使得当前价值相对不选更大,那就选取 i,更新当前最大价值
            if ((j >= C[i]) && (dp[i][j] < dp[i-1][j - C[i]] + W[i])) {
    
    
                dp[i][j] = dp[i-1][j - C[i]] + W[i];
            }
        }
    }
    // 返回,对于所有物品(0~N),背包容量为 V 时的最大价值
    return dp[n][V];
}
  • 一维数组版
    当前行的遍历用到的值是上一行的前面列的值,如果我们第二层 for 循环遍历的时候倒着遍历的话,保证了前面更新的值不会被新计算的值覆盖掉,仅仅用一维数组就可以解决问题,对空间进行了优化
public static int zeroOnePackOpt(int V, int[] C, int[] W) {
    
     
    // 防止无效输入
    if ((V <= 0) || (C.length != W.length)) {
    
    
        return 0;
    }

    int n = C.length;

    int[] dp = new int[V + 1];

    // 背包空的情况下,价值为 0
    dp[0] = 0;

    for (int i = 1; i <= n; ++i) {
    
    
        for (int j = V; j >= C[i]; --j) {
    
    
            dp[j] = Math.max(dp[j], dp[j - C[i]] + W[i]);
        }
    }

    return dp[V];
}

模拟过程

推荐一个视频,这是b站上一个up主做的,只有3分钟,他把这个过程用动画形式表现了出来,我觉得是我看了那么多解释里解释的最好的,链接如下: 0-1背包问题

数据
在这里插入图片描述
 
具体过程(截取了比较有代表性的几步)
在这里插入图片描述
在这里插入图片描述
最终结果
在这里插入图片描述

P.S 这个up主竟然是个高中生,惊呆
 

邮资问题

牛客网-BIT复试真题

某人有8 角的邮票5 张,1 元的邮票4 张,1 元8 角的邮票6 张,用这些邮票中的一张或若干张可以得到多少中不同的邮资?
 

题解

动态规划

  • 把这道题想成求是否有恰好装满容量为8,9,…188的背包的方案,8和188是题目所给邮资的最小和最大值,这也是0-1背包问题的一种经典问法,代码如下:
#include<iostream>
using namespace std;
int main()
{
    
    
    int f[189]={
    
    1};
    int a[] = {
    
    0,8,8,8,8,8,10,10,10,10,18,18,18,18,18,18};
    //f[j]即邮资为j的方案数
    for(int i=1;i<16;i++)
    {
    
    
        for(int j=188;j>=a[i];j--)
            f[j]+=f[j-a[i]];//最重要的一步
    }
    int count=0;//统计邮资情况总数
    //f[i]不为0,则说明邮资为i有方案,计入count
    for(int i=1;i<189;i++)
    {
    
    
        if(f[i])
            count++;
    }
    cout<<count;
}

暴力破解

  • 当然暴力破解也是可以的,可以利用set去重,代码如下:
#include<iostream>
#include<set>
using namespace std;
int main()
{
    
    
    set<int>num;
    for(int i=0;i<=5;i++)
    {
    
    
        for(int j=0;j<=4;j++)
        {
    
    
            for(int k=0;k<=6;k++)
            {
    
    
                //至少一张邮票,排除i,j,k都为0的情况
                if(i+j+k==0)
                    continue;
                num.insert(8*i+10*j+18*k);
            }
        }
    }
    cout<<num.size();
}

猜你喜欢

转载自blog.csdn.net/qq_43417265/article/details/112390540