完全背包问题

6 篇文章 0 订阅

问题描述:

       有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。

完全背包问题的特点是:每个物品可以放入的个数是无限的。这不同于01背包问题的每个物品只有一个的特点

原理和01背包问题差不多,所以解题的步骤也差不多。

首先,定义状态d[i][j]:d[i][j]表示前i个物品装到剩余体积为j的背包中的最大价值

接下来确定状态转移方程,先来回顾一下01背包问题的状态转移方程:
这里写图片描述
d[i][j]就是考虑第i个物品要不要放入背包:

  1. 首先,若第i个物品的体积w[i]大于剩余可用体积j的话,那自然不可以放入,那么d[i][j]就等于d[i-1][j],因为第i个物品无法放入,那么d[i][j]的最大价值就和d[i-1][j]相同
  2. 若第i个物品的体积w[i]小于等于剩余可用体积j的话,那么可以选择放入,可以选择不放入,这时候就要看放入和不放入时哪个的最大价值更高。也就是上面方程中max{d[i-1][j],d[i-1][j-w[i]+v[i]]}

由上面的01背包问题的状态转移方程,我们很容易得到完全背包问题的状态转移方程:
这里写图片描述
怎么理解呢?k表示第i个物品放入几个,那么k*w[i]就是k个i物品占据的体积,k*v[i]就是k个i物品的价值
k取值是有限制条件的,即0<=k*w[i]<=j,也就是必须小于剩余可用体积j

当k=0时,就是01背包问题中的不放入第i个物品
当k=1是,就是01背包问题中的放入第i个物品
当然,在完全背包问题中,k还可以取值2,3,4,….只需要满足0<=k*w[i]<=j即可

根据上面的思路,很容易参考01背包问题的核心代码来写出完全背包问题的核心代码:

for (int i = 1; i <= N; i++) {
    int w = sc.nextInt();
    int v = sc.nextInt();
    for (int j = 0; j <= W; j++) {
        for (int k = 0; k*w <= j; k++) {
            d[i][j] = d[i-1][j]>d[i-1][j-k*w]+k*v?d[i-1][j]:d[i-1][j-k*w]+k*v;
        }
    }
}

和01背包问题的核心代码比较,就是多了一个k的for循环
完整代码如下:

import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt();
        int W = sc.nextInt();
        int[][] d = new int[N+1][W+1];
        for (int i = 1; i <= N; i++) {
            int w = sc.nextInt();
            int v = sc.nextInt();
            for (int j = 0; j <= W; j++) {
                for (int k = 0; k*w <= j; k++) {
                    d[i][j] = d[i-1][j]>d[i-1][j-k*w]+k*v?d[i-1][j]:d[i-1][j-k*w]+k*v;
                }
            }
        }
        System.out.println(d[N][W]);
    }
}

测试:
输入:
3 5
3 5
2 10
2 20
输出:
40
这里写图片描述

代码优化

完全背包问题也可以将二维数组改成一维数组

import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt();
        int W = sc.nextInt();
        int[] d = new int[W+1];
        for (int i = 1; i <= N; i++) {
            int w = sc.nextInt();
            int v = sc.nextInt();
            for (int j = W; j >= 0; j--) {
                for (int k = 0; k*w <= j; k++) {
                    d[j] = d[j]>d[j-k*w]+k*v?d[j]:d[j-k*w]+k*v;
                }
            }
        }
        System.out.println(d[W]);
    }
}

接下来解决一个实际问题:

题目描述:

POJ 2063 Investment
你刚开始拥有金钱 x (x <= 1000 000) 现在有 n (1 <= n <= 10) 种债券可以投资
每种债券的售价均是1000的整数倍且年收益不超过售价的 10% 现在 n 种债券的售价和年收益已给出 问 y 年后你最多拥有多少钱 ( y <= 40)
债券的购买数量不设上限 例如你拥有 2000 元 有一种债券售价 1000 元 年收益是 100 元 那么你可以买 2 份这种债券 一年后你拥有的钱是
2000(两份债券的价值) + 100 * 2(每份债券收益100元) = 2200 每一年收益计算后你可以重新调整你拥有的债券的情况 即把收益较少的债券卖出去用
得到的钱和收益的钱买收益更好的债券
样例:
输入:
1 //有多少组测试数据
10000 4 //10000代表初始金额,4代表4年之后的最大收益
2 //有两种股票
4000 400 //接下来2行,每行分别代表每种股票的价值与年收益
3000 250
输出:
14050

这是一个很明显的完全背包问题

思路分析:

首先,定义状态d[i][j]:d[i][j]表示可购买的债券为前i个可投资金额为j的情况下可获得的最大收益,时间是一年。

接下来定义状态转移方程:

d[i][j] = max{d[i-1][j-k*vest[i]]+k*value[i],d[i-1][j]}

完整代码如下:

import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        for (int i = 0; i < sc.nextInt(); i++) {
            int money = sc.nextInt();
            int year = sc.nextInt();
            int N = sc.nextInt();
            int[] vest = new int[N+1];
            int[] value = new int[N+1];
            int[][] d = new int[N+1][100];
            for (int j = 1; j < N+1; j++) {
                vest[j] = sc.nextInt();
                value[j] = sc.nextInt();
            }
            int maxValue = money;
            for (int j = 0; j < year; j++) {
                maxValue += investion(maxValue,d,vest,value);
            }
            System.out.println(maxValue);
        }
    }
    private static int investion(int money,int[][] d,int[] vest,int[] value){
        for (int i = 1; i < vest.length; i++) {
            for (int j = 0; j <= money; j+=1000) {
                int max = 0;
                for (int k = 0; k*vest[i] <= j; k++) {
                    int a = d[i-1][j/1000];
                    int b = d[i-1][j/1000-k*vest[i]/1000]+k*value[i];
                    int tmp = a>b?a:b;
                    max = max>tmp?max:tmp;
                }
                d[i][j/1000] = max;
            }
        }
        return d[vest.length-1][money/1000];
    }
}

因为d[i][j]求的是一年的收益,所以第二年的投资金额就是第一年的本金+收益,第三年的投资金额就是第二年的本金+收益。也就是上面的这几行代码:

int maxValue = money;
for (int j = 0; j < year; j++) {
    maxValue += investion(maxValue,d,vest,value);
}

初始投资金额maxValue等于输入的初始金额money,然后循环year次,每次的初始金额等于上一年的本金+上一年的收益。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值