完全背包(动态规划)

本文详细介绍了完全背包问题的四种解题思路及其对应的程序实现,包括状态转移方程和时间复杂度分析。

Description

设有n 种物品,每种物品有一个重量及一个价值。但每种物品的数量是无限的,同时有一个背包,最大载重量为M,今从n 种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于M,而价值的和为最大。

Input

第一行:两个整数,M(背包容量,M<= 200)和N(物品数量,N<= 30); 第2..N+1 行:每行二个整数Wi,Ui,表示每个物品的重量和价值。

Output

仅一行,一个数,表示最大总价值。

Sample Input

 

12 



 9

 

Sample Output

 

15


解题思路1:f[i,j]表示前i种物品在容量为j的背包里的最大价值,状态转移方程为:

f[i,j]=max{f[I,j-w[i]]+u[i],f[i-1,j]}

(1<=i<=n,w[i]<=j<=m)

时间复杂度: O(nm)


程序1:
const
  maxm=200;
  maxn=30;
var
  i,j,n,m:longint;
  w,u:array[1..maxn]of longint;
  f:array[0..maxn,0..maxm]of longint;

function max(x,y:longint):longint;
  begin
    if x>y then exit(x);
    exit(y);
end;

begin
  readln(m,n);
  for i:=1 to n do
    readln(w[i],u[i]);
  for i:=1 to n do
    begin
      f[i]:=f[i-1];
      for j:=w[i] to m do
        f[i,j]:=max(f[i,j-w[i]]+u[i],f[i-1,j]);
    end;
  writeln(f[n,m]);
end.


解题思路2: f[j] 表示上一篇解题报告中的 f[i,j] ,状态转移方程为:

f[j]=max{f[j-w[i]]+u[i],f[j]}

(1<=i<=n,w[i]<=j<=m)

时间复杂度: O(nm)



程序2:
const
  maxm=200;
  maxn=30;
var
  i,j,n,m:longint;
  w,u:array[1..maxn]of longint;
  f:array[0..maxm]of longint;

function max(x,y:longint):longint;
  begin
    if x>y then exit(x);
    exit(y);
end;

begin
  readln(m,n);
  for i:=1 to n do
    readln(w[i],u[i]);
  for i:=1 to n do
    for j:=w[i] to m do
      f[j]:=max(f[j-w[i]]+u[i],f[j]);
  writeln(f[m]);
end.


解题思路3: f[i,j] 表示前 i 种物品在容量为 j 的背包里的最大价值,状态转移方程为:

f[i,j]=max{f[i,j-k*w[i]]+k*u[i],f[i-1,j]}

(1<=i<=n,w[i]<=j<=m,0<=k*w[i]<=j)

时间复杂度: O(nm^2)


程序3:
const
  maxm=200;
  maxn=30;
var
  i,j,k,n,m:longint;
  w,u:array[1..maxn]of longint;
  f:array[0..maxn,0..maxm]of longint;

function max(x,y:longint):longint;
  begin
    if x>y then exit(x);
    exit(y);
end;

begin
  readln(m,n);
  for i:=1 to n do
    readln(w[i],u[i]);
  for i:=1 to n do
    begin
      f[i]:=f[i-1];
      for j:=w[i] to m do
        for k:=0 to j div w[i] do
          f[i,j]:=max(f[i,j-k*w[i]]+k*u[i],f[i-1,j]);
    end;
  writeln(f[n,m]);
end.



解题思路4: 这是上一种方法的优化,把 k 次循环缩减为 2^x 次方,就是进行预处理,把完全背包转化为 0/1 背包。
时间复杂度: O(n m㏒m)


程序4:
const
  maxm=200;
  maxn=30;
var
  i,j,n,m,k,z:longint;
  w,u:array[1..maxn]of longint;
  f,a,b:array[0..maxm]of longint;

function max(x,y:longint):longint;
  begin
    if x>y then exit(x);
    exit(y);
end;

begin
  readln(m,n);
  for i:=1 to n do
    readln(w[i],u[i]);
  for i:=1 to n do
    begin
      k:=1;
      while k*w[i]<=m do
        begin
          inc(z);
          a[z]:=w[i]*k;
          b[z]:=u[i]*k;
          k:=k*2;
        end;
    end;
  n:=z;
  for i:=1 to n do
    for j:=m downto a[i] do
      f[j]:=max(f[j-a[i]]+b[i],f[j]);
  writeln(f[m]);
end.



版权属于: Chris
转载时必须以链接形式注明原始出处及本声明。
<think>我们讨论的是完全背包问题,其特点是每种物品有无限多个。与01背包问题(每种物品只能选一次)不同,在完全背包问题中,我们可以在不超过背包容量的前提下,无限次选取每种物品。 动态规划解法: 状态定义:dp[j] 表示容量为 j 的背包能够装下的最大价值。 状态转移:对于每种物品 i,体积为 v_i,价值为 w_i,我们考虑在背包容量允许的情况下,可以多次选取该物品。因此,在更新 dp[j] 时,我们使用正序遍历(与01背包的逆序相反),这样可以在更新当前状态时利用当前物品已经更新过的状态(即允许重复选取)。 状态转移方程: dp[j] = max(dp[j], dp[j - v_i] + w_i) 当 j >= v_i 模板代码(空间优化版): for i in range(n): # 遍历物品 for j in range(v_i, V+1): # 从小到大遍历背包容量 dp[j] = max(dp[j], dp[j - v_i] + w_i) 注意:这里内层循环是正序的,这样在更新dp[j]时,dp[j - v_i]可能已经更新过(即可能已经包含当前物品),因此可以实现重复选取。 下面我们给出一个完整的C++模板代码示例:</think># 完全背包问题动态规划实现模板 完全背包问题是一种经典的动态规划问题,其特点是**每种物品可以无限次使用**。与01背包问题(每种物品仅能使用一次)不同,完全背包问题需要调整状态转移策略来适应物品的无限次使用特性。 ## 核心算法思想 1. **问题定义**: - 给定背包容量 $V$ 和 $n$ 种物品 - 每种物品 $i$ 有体积 $v_i$ 和价值 $w_i$ - 目标:在不超过背包容量的前提下,最大化物品总价值 2. **状态定义**: - $dp[j]$:容量为 $j$ 的背包所能达到的最大价值 3. **状态转移方程**: $$ dp[j] = \max(dp[j], dp[j - v_i] + w_i) \quad \text{for } j = v_i \text{ to } V $$ 4. **关键区别**: - 01背包:内层循环**逆序**遍历(防止重复选取) - 完全背包:内层循环**顺序**遍历(允许重复选取) ## 空间优化实现模板(一维数组) ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; int completeKnapsack(int V, vector<int>& v, vector<int>& w) { vector<int> dp(V + 1, 0); // 初始化dp数组 // 遍历所有物品 for (int i = 0; i < v.size(); i++) { // 顺序遍历背包容量(关键区别) for (int j = v[i]; j <= V; j++) { dp[j] = max(dp[j], dp[j - v[i]] + w[i]); } } return dp[V]; } int main() { // 示例数据 int V = 10; // 背包容量 vector<int> v = {2, 3, 4, 5}; // 物品体积 vector<int> w = {3, 4, 5, 6}; // 物品价值 int maxValue = completeKnapsack(V, v, w); cout << "最大价值: " << maxValue << endl; return 0; } ``` ## 二维数组实现模板(更易理解) ```cpp int completeKnapsack2D(int V, vector<int>& v, vector<int>& w) { int n = v.size(); vector<vector<int>> dp(n + 1, vector<int>(V + 1, 0)); for (int i = 1; i <= n; i++) { for (int j = 0; j <= V; j++) { // 不选当前物品 dp[i][j] = dp[i - 1][j]; // 选当前物品(可重复选) if (j >= v[i - 1]) { dp[i][j] = max(dp[i][j], dp[i][j - v[i - 1]] + w[i - 1]); } } } return dp[n][V]; } ``` ## 算法分析 1. **时间复杂度**: - $O(n \times V)$,其中 $n$ 是物品种类数,$V$ 是背包容量 2. **空间复杂度**: - 一维数组:$O(V)$ - 二维数组:$O(n \times V)$ 3. **正确性证明**: - 顺序遍历确保每个物品可被多次选择 - 状态转移方程保证最优子结构性质 ## 相关问题 1. **基础应用**: - 如何修改代码记录选择的物品组合? (添加选择路径追踪数组) - 当背包必须装满时,如何调整初始化? ($dp[0]=0$,其余初始化为负无穷) 2. **变种问题**: - 完全背包的最小物品数问题:求填满背包所需最少物品数量 (将 $\max$ 改为 $\min$,$w_i$ 设为1) - 组合问题:求填满背包的方案数 (将 $\max$ 改为加法,初始 $dp[0]=1$) 3. **性能优化**: - 当 $V$ 很大时如何优化? (使用滚动数组或数学优化) - 如何利用物品体积的整除关系优化? (预处理体积关系,减少状态数) 4. **实际应用场景**: - 硬币找零问题:给定面额组合求找零方案 (完全背包的典型应用) - 资源分配问题:在预算限制下最大化收益 5. **算法对比**: - 完全背包与01背包的核心区别是什么? (物品选取次数限制导致遍历顺序不同) - 多重背包问题如何转化为完全背包? (当物品数量 $c_i \times v_i > V$ 时)[^1] [^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值