0-1背包 分析: $$ dp[i][j]意思是只放前i个物品在总体积是j的情况下的物品的贡献 $$
$$ dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]+w[i]]) $$
$$ dp[i][j]=dp[i-1][j]意思是不选第i个物品(dp[i][j]直接原封不动保留上一个i的状态) $$
代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <iostream> #include <vector> #include <cstring> #include <algorithm> using namespace std;typedef long long ll;int n,m,v[1005 ],w[1005 ],dp[1005 ][1005 ];int main () { cin>>n>>m; for (int i = 1 ; i <=n ; ++i) { cin>>v[i]>>w[i]; } for (int i = 1 ; i <=n; ++i) { for (int j =0 ; j <=m ; ++j) { dp[i][j] = dp[i - 1 ][j]; if (j >= v[i]) dp[i][j] = max (dp[i][j], dp[i - 1 ][j - v[i]] + w[i]); } } int res=0 ; for (int i = 0 ; i <=m ; ++i) { res=max (res,dp[n][i]); } cout<<res<<endl; }
4 5 1 2 2 4 3 4 4 5
0 2 2 2 2 2 0 2 4 6 6 6 0 2 4 6 6 8 0 2 4 6 6 8
优化后的代码: ==只用了一维==
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> #include <vector> #include <cstring> #include <algorithm> using namespace std;typedef long long ll;int n,m,v[1005 ],w[1005 ],dp[1005 ];int main () { cin>>n>>m; for (int i = 1 ; i <=n ; ++i) { cin>>v[i]>>w[i]; } for (int i = 1 ; i <=n; ++i) { for (int j =m; j >=v[i] ; --j) { dp[j] = max (dp[j], dp[j - v[i]] + w[i]); } } cout<<dp[m]; }
2 2 2 2 2 6 6 6 4 8 6 6 8 6 8
==注:若要求达到最大价值时的最大体积(恰好)需将初始化dp[N]={0}改为dp[0]=0,剩余dp为-INF; ans从直接输出dp[m]变为max(dp[0…m]).==
状态方程的详细求解过程:
二维优化成一维的推导:
完全背包 $dp[i][j]$同样也是表示从前i个物品中选,总体积不超过j的方案集合,唯一的区别就是完全背包问题中每个物品都可以取无限个
所以可得到: $$ dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i],dp[i-1][j-2v[i]]+2 w[i],…) $$ 按道理来说需要三层循环,但是可以被优化成二层循环: $$ dp[i][j]=max(dp[i-1][j],dp[i][j-v[i]]+w[i]) $$
具体证明:
代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <bits/stdc++.h> using namespace std;typedef long long ll;ll n,m,dp[1005 ][1005 ]; int main () { ios::sync_with_stdio (false ); cin.tie (0 ); int n,m,v[1005 ],w[1005 ]; cin>>n>>m; for (int i=1 ;i<=n;i++)cin>>v[i]>>w[i]; for (int i=1 ;i<=n;i++){ for (int j=0 ;j<=m;j++){ dp[i][j]=dp[i-1 ][j]; if (j>=v[i])dp[i][j]=max (dp[i][j],dp[i][j-v[i]]+w[i]); } } cout<<dp[n][m]; }
二维优化成一维: $$ dp[i][j]=max(dp[i-1][j],dp[i][j-v[i]]+w[i])\to dp[j]=max(dp[j],dp[j-v[i]]+w[i]) $$
与0-1背包不同的是,0-1背包的$dp[j-v[i]]$对应的是i上一层也就是i-1层的值,而这里的$dp[i][j-v[i]]$要求对应本层i的值,也就是让$dp[j-v[i]]$先更新再更新$dp[j]$,所以把j从大到小循环改为从小到大循环即可。
优化后的代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <bits/stdc++.h> using namespace std;typedef long long ll;ll n,m,dp[1005 ]; int main () { ios::sync_with_stdio (false ); cin.tie (0 ); int n,m,v[1005 ],w[1005 ]; cin>>n>>m; for (int i=1 ;i<=n;i++)cin>>v[i]>>w[i]; for (int i=1 ;i<=n;i++){ for (int j=v[i];j<=m;j++){ dp[j]=max (dp[j],dp[j-v[i]]+w[i]); } } cout<<dp[m]; }
多重背包 朴素做法(三维): 多一维遍历第i个物品选$0~s[i]$个的所有情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> using namespace std;int main () { ios::sync_with_stdio (false ); int n,m; cin>>n>>m; int v[105 ],w[105 ],s[105 ],dp[105 ]={0 }; for (int i = 1 ; i <=n ; ++i) { cin>>v[i]>>w[i]>>s[i]; } for (int i = 1 ; i <=n ; ++i) { for (int j = m; j >=v[i] ; --j) { for (int k = 1 ; k <=s[i] ; ++k) { if (j>=k*v[i])dp[j]=max (dp[j],dp[j-k*v[i]]+k*w[i]); } } } cout<<dp[m]<<endl; }
思考到多重背包问题可以将每个物品拆开然后重新存入数组里,但如果直接拆开根据数据范围$sN V=20001000 2000=2*10^9$
还是会超过时间限制,所以考虑到将物品通过二进制的方法拆解:假如某物品有7件用1,2,4就可以表示全部可能。所以得出s件物品只需要$\lceil log(s) \rceil$的时间,所以就是$111000 2000\approx2*10^7$,可以在规定时间内通过。但在具体实现中还会有问题,假如某物品有10件,不能直接用1,2,4,8表示,因为1,2,4,8可以表示1~15,而11~15不是我们所需要的。所以采取一个策略:用s减去1,2,4…当s<0的时候停止,并将最后剩余的那个数存进数组。假如10,$10-1-2-4=3$,所以就将1,2,4,3存入数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> #include <vector> using namespace std;struct struc { int v, w; }; int v[2005 ], w[2005 ], s[2005 ], dp[2005 ];vector<struc> vec; int main () { ios::sync_with_stdio (false ); int n, m; cin >> n >> m; for (int i = 1 ; i <= n; ++i) { cin >> v[i] >> w[i] >> s[i]; } for (int i = 1 ; i <= n; ++i) { for (int k = 1 ; k <= s[i]; k *= 2 ) { s[i] -= k; vec.push_back ({k * v[i], k * w[i]}); } if (s[i] > 0 )vec.push_back ({s[i] * v[i], s[i] * w[i]}); } for (int i = 0 ; i < vec.size (); ++i) { for (int j = m; j >= vec[i].v; --j) { dp[j] = max (dp[j], dp[j - vec[i].v] + vec[i].w); } } cout << dp[m] << endl; }
分组背包 解析: 代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> #define pb push_back using namespace std;typedef long long ll;int n,m,dp[105 ],s[105 ],v[105 ][105 ],w[105 ][105 ];int main () { cin>>n>>m; for (int i = 1 ; i <=n ; ++i) { cin>>s[i]; for (int j = 1 ; j <=s[i] ; ++j) { cin>>v[i][j]>>w[i][j]; } } for (int i = 1 ; i <=n ; ++i) { for (int j = m; j >=0 ; --j) { for (int k = 1 ; k <=s[i] ; ++k) { if (v[i][k]<=j)dp[j]=max (dp[j],dp[j-v[i][k]]+w[i][k]); } } } cout<<dp[m]; }