01背包问题

· · 个人记录

背包问题可分三种,01背包、完全背包以及多重背包。

什么是01背包?

在“01背包问题”中,f_{ij}记录“将前i件物品放入容量为j的背包中”的最大价值,考虑当前第i件物品的策略(放或不放),如果不放的i件物品,那么问题就转化为“前i-1件物品放入容量为j的背包中”,最大价值为f_{i-1,j};如果放第i件物品,那么问题就转化为“前i-1件物品放入剩下的容量为j-v_i的背包中”,此时能获得的最大价值就是f_{i-1,j-v_i}再加上放入第i件物品获得的价值p_i

我们来看一道题:

P1060 开心的金明

1.状态的设计:

|花费$/$元|0|1|2|3|4|5|6|7|8|9|10|边界| |:----------:|:----------:|:----------:|:----------:|:----------:|:----------:|:----------:|:----------:|:----------:|:----------:|:----------:|:----------:|:----------:| |阶段0|0|0|0|0|0|0|0|0|0|0|0|| |阶段1|0|0|0|0|0|0|0|0|1600|1600|1600|取价格$800$的物品| |阶段2|0|0|0|0|2000|2000|2000|2000|2000|2000|2000|取价格$400$的物品| |阶段3|0|0|0|1500|2000|2000|2000|3500|3500|3500|3500|取价格$300$的物品| |阶段4|0|0|0|1500|2000|2000|2000|3500|3500|3500|3500|取价格$400$的物品| |阶段5|0|0|400|1500|2000|2000|2400|3500|3500|3900|3900|取价格$200$的物品| **2.状态转移方程:** $f_{i,j}=max(f_{i-1,j-v_i}+v_i \times w_i,f_{i-1,j})$($1\le i\le m,v_i\le j\le n$) $f_{i,0}=0$($1\le i\le m$),$f_{0,j}=0$($0\le j\le n$) 该算法的时间复杂度为$\Theta(nm)$,参考程序如下: ```cpp #include<iostream> using namespace std; int n,m,v[27],w[27],f[27][30002]; int main(){ cin>>n>>m; for(int i=1;i<=m;i++) cin>>v[i]>>w[i]; for(int i=1;i<=m;i++) for(int j=1;j<=n;j++) if(j>=v[i])f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+v[i]*w[i]); else f[i][j]=f[i-1][j]; cout<<f[m][n]<<endl; return 0; } ``` **3.优化空间:** 以上方法的空间复杂度为$\Theta(nm)$,可以优化到$\Theta(n)$。 首先,我们知道$f_{i,j}$是由$f_{i-1,j}$和$f_{i-1,j-v_i}$两个子问题决策而来;也就是说第$i$阶段只与第$i-1$阶段有关,可以使用滚动数组来优化空间。 继续分析,若将$f_{i,j}$改为一维数组$g_j$,并将上面程序中$for(int$ $j=1;j<=n;j++)$循环改为: $for(int$ $j=n;j>=v[i];j--)$,这样顺序计算$g_i$时,可以保证$g_{j-v_i}$保存的是前一阶段的状态$f_{i-1,j-v_i}$的值。 改进的程序如下: ```cpp #include<bits/stdc++.h> using namespace std; int n,m,v[27],w[27],f[30002]; int main() { cin>>n>>m; for(int i=1;i<=m;i++) cin>>v[i]>>w[i]; for(int i=1;i<=m;i++) for(int j=n;j>=v[i];j--) f[j]=max(f[j],f[j-v[i]]+v[i]*w[i]); cout<<f[n]; return 0; } ``` **其他$01$背包的题目:** [P1048 采药](https://www.luogu.com.cn/problem/P1048) **小结:** $01$背包是最基本的背包问题,它包含了背包问题中划分阶段、确定状态、建立状态转移方程的基本思想,其他类型的背包问题往往也可以转化成$01$背包问题求解