2 solutions

  • 7
    @ 2025-4-30 19:24:27

    前言

    推荐我的博客:《背包详解》

    更新日志

    To be updated...

    什么是背包?

    动态规划中的背包问题主要分为01 背包完全背包两种经典模型:

    • 01 背包:每个物品可以选或者不选,即选 1100 次。
    • 完全背包:完全背包模型与 01 背包类似,与 01 背包的区别仅在于一个物品可以选取无限次,而非仅能选取一次。

    01 背包

    例题:洛谷 P2871 [USACO07DEC] Charm Bracelet S

    nn 个物品和一个容量为 WW 的背包,每个物品有重量 wiw_{i} 和价值 viv_{i} 两种属性,要求选若干物品放入背包使背包中物品的总价值最大且背包中物品的总重量不超过背包的容量。

    看到这题首先想到贪心,然后考虑怎么贪?

    按照重量?价值?性价比?我们都很容易想到反例,贪心失败。

    然后我们考虑搜索。显然的枚举子集,可是时间复杂度 O(2n)O(2^n),T 飞了,别想了。但是这样还有 3737 分,显然数据水了。

    接下来是正解,动态规划

    fi,jf_{i,j} 为在只能放前 ii 个物品的情况下,容量为 j 的背包所能达到的最大总价值。

    考虑转移。假设当前已经处理好了前 i1i-1 个物品的所有状态,那么对于第 ii 个物品,当其不放入背包时,背包的剩余容量不变,背包中物品的总价值也不变,故这种情况的最大价值为 fi1,jf_{i-1,j};当其放入背包时,背包的剩余容量会减小 wiw_{i},背包中物品的总价值会增大 viv_{i},故这种情况的最大价值为: fi1,jwi+vif_{i-1,j-w_{i}}+v_{i}

    由此可以得出状态转移方程:fi,j=max(fi1,j,fi1,jwi+vi)f_{i,j}=\max(f_{i-1,j},f_{i-1,j-w_{i}}+v_{i})

    然后我们就可以顺利的写出代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 13000;
    int w[N], v[N], f[N][N], n, m;
    
    int main() 
    {
    	cin >> n >> m;
    	for (int i = 1; i <= n; ++i) cin >> w[i] >> v[i];
    	for (int i = 1; i <= n; ++i)
    	{
    		for (int j = 1; j <= m; ++j)
    		{
    			f[i][j] = f[i - 1][j]; 
    			if (j >= w[i]) f[i][j] = max(f[i][j], f[i - 1][j - w[i]] + v[i]);  
    		}
    	}
    	cout << f[n][m] << endl;
        return 0;
    }
    

    但是,一看 8282 分,两个点 MLE。我们充分发挥人类智慧,二位数组可能会炸空间,可以考虑改用滚动数组的形式来优化

    由于对 fif_i 有影响的只有 fi1f_{i-1},可以去掉第一维,直接用 fif_{i} 来表示处理到当前物品时背包容量为 ii 的最大价值,得出以下方程:fj=max(fj,fjwi+vi)f_j=\max \left(f_j,f_{j-w_i}+v_i\right)

    然后我们就可以顺利的写出代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 13000;
    int n, m, w[N], v[N], f[N];
    
    int main()
    {
      	cin >> n >> m;
      	for (int i = 1; i <= n; i++) cin >> w[i] >> v[i];
      	for (int i = 1; i <= n; i++)
        	for (int j = m; j >= w[i]; j--)
          		if (f[j - w[i]] + v[i] > f[j])
    			  	f[j] = f[j - w[i]] + v[i];
      	cout << f[m] << endl;
      	return 0;
    }
    

    显然可以 AC。

    完全背包

    完全背包模型与 01 背包类似,与 01 背包的区别仅在于一个物品可以选取无限次,而非仅能选取一次。

    我们可以借鉴 01 背包的思路,进行状态定义:设 fi,jf_{i,j} 为只能选前 ii 个物品时,容量为 jj 的背包可以达到的最大价值。

    需要注意的是,虽然定义与 01 背包类似,但是其状态转移方程与 01 背包并不相同。

    可以发现,对于 fi,jf_{i,j},通过 fi,jwif_{i,j-w_i} 转移即可。因此状态转移方程为:fi,j=max(fi1,j,fi,jwi+vi)f_{i,j}=\max(f_{i-1,j},f_{i,j-w_i}+v_i)。理由是当我们这样转移时,fi,jwif_{i,j-w_i} 已经由 fi,j2×wif_{i,j-2\times w_i} 更新过,那么 fi,jwif_{i,j-w_i} 就是充分考虑了第 ii 件物品所选次数后得到的最优结果。换言之,我们通过局部最优子结构的性质重复使用了之前的枚举过程,优化了枚举的复杂度。

    然后我们就可以顺利的写出代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 1e4 + 5, M = 1e7 + 5;
    int n, m, w[N], v[N];
    long long f[M];
    
    int main()
    {
      	cin >> m >> n;
      	for (int i = 1; i <= n; i++) cin >> w[i] >> v[i];
      	for (int i = 1; i <= n; i++)
        	for (int j = w[i]; j <= m; j++)
          	if (f[j - w[i]] + v[i] > f[j])
    		  	f[j] = f[j - w[i]] + v[i];
      	cout << f[m];
      	return 0;
    }
    

    参考文献

    后记

    背包还是比较难的,大家不要急于抄题解,要去理解!

    谢谢观看Thanks♪(・ω・)ノ

    • 4
      @ 2025-4-30 19:32:59

      01背包与完全背包问题详解

      1. 背包问题

      动态规划中的背包问题主要分为01背包完全背包两种经典模型:

      01背包 完全背包
      每个物品只能选0次或1次 每个物品可以无限次选取

      2. 01背包

      问题描述

      给定NN件物品和容量为VV的背包,

      物品ii的重量为WiW_{i},价值为ViV_{i}

      求不超过背包容量的最大价值总和


      动态规划方程

      dp[i][j]dp[i][j]表示ii个物品=放入容量jj背包的最大价值:

      表达式为

      $$ dp[i][j] = \begin{cases} dp[i-1][j], & j < w[i] \\ \max(dp[i-1][j],\ dp[i-1][j-w[i]] + v[i]), & j \geq w[i] \end{cases} $$

      实现

      1.二维数组版

      int f[10000][10000],v[1919810],w[1919810],m,n;
      void beibao(){
          for(int i=1;i<=n;i++){
              cin>>v[i];
              cin>>w[i];
              for(int j=0;j<=m;j++){
                  f[i][j]=f[i-1][j];
                  v[i]<=j ? f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]):f[i][j]=f[i][j];
                  //在加上 v[i] 后小于等于总体积j 即v+v[i]<=j
              }
          }
      }
      
      

      如果82wa就是二维数组爆炸

      2.一维优化版

      int f[1919810],v[1919810],w[1919810],m,n;
      void beibao() {
          for(int i=1;i<=n;i++){
              cin>>v[i]>>w[i];
              for(int j=m;j>=v[i];j--) {
                  f[j]=max(f[j],f[j-v[i]]+w[i]);
              }
          }
      }
      
      

      3. 完全背包问题

      P1616 疯狂的采药

      题目背景

      此题为纪念 LiYuxiang 而生。

      题目描述

      LiYuxiang 是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同种类的草药,采每一种都需要一些时间,每一种也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”

      如果你是 LiYuxiang,你能完成这个任务吗?

      此题和原题的不同点:

      11. 每种草药可以无限制地疯狂采摘。

      22. 药的种类眼花缭乱,采药时间好长好长啊!师傅等得菊花都谢了!

      输入格式

      输入第一行有两个整数,分别代表总共能够用来采药的时间 tt 和代表山洞里的草药的数目 mm

      22 到第 (m+1)(m + 1) 行,每行两个整数,第 (i+1)(i + 1) 行的整数 ai,bia_i, b_i 分别表示采摘第 ii 种草药的时间和该草药的价值。

      输出格式

      输出一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。

      输入输出样例 #1

      输入 #1

      70 3
      71 100
      69 1
      1 2
      

      输出 #1

      140
      

      说明/提示

      数据规模与约定

      • 对于 30%30\% 的数据,保证 m103m \le 10^3
      • 对于 100%100\% 的数据,保证 1m1041 \leq m \le 10^41t1071 \leq t \leq 10^7,且 1m×t1071 \leq m \times t \leq 10^71ai,bi1041 \leq a_i, b_i \leq 10^4

      表达式为

      $$ dp[i][j] = \begin{cases} dp[i-1][j], & j < w[i] \\ \max(dp[i-1][j],\ dp[i][j-w[i]] + v[i]), & j \geq w[i] \end{cases} $$

      实现

      void beibao() {
          for(int i=1;i<=n;i++){
              cin>>v[i]>>w[i];
              for(int j=v[i];j<=m;j++){
                  f[j]=max(f[j],f[j-v[i]]+w[i]);
                  maxn=max(f[j],maxn);
              }
          }
      
      
      

      关键点:正序遍历允许重复选取


      4. 对比

      01背包 完全背包
      物品选取规则 每个物品选0/1 每个物品可无限
      遍历顺序 逆序(防止重复) 正序(允许重复)
      状态转移 dp[i1][jw]+vdp[i-1][j-w]+v dp[i][jw]+vdp[i][j-w]+v

      5. 复杂度

      二维数组 一维数组
      时间复杂度 O(NV)O(NV) O(NV)O(NV)
      空间复杂度 O(V)O(V)
    • 1

    Information

    ID
    1928
    Time
    1000ms
    Memory
    128MiB
    Difficulty
    2
    Tags
    # Submissions
    100
    Accepted
    13
    Uploaded By