目录
简论
关于dp问题 :
编辑
0-1背包问题
定义 :
例题 :
题面 :
编辑
思路 :
代码(二维) :
代码(一维优化版):
完全背包问题
题目链接 :
题面 :
编辑
思路 :
代码(朴素) :
代码(优化) :
代码(一维优化) :
多重背包问题
题目链接 :
题面 :
编辑
思考 :
代码 (朴素):
多重背包问题II
链接 :
思路 :
代码(二进制优化) :
分组背包问题
题目链接 :
思考 :
代码 (朴素):
代码(一维优化) :
简论
这篇将关于动态规划问题中的背包问题(上),包括,01背包,完全背包,多重背包,分组背包的朴素代码和其优化的详细解答,主要基于acwing算法基础课动态规划讲解,欢迎留言讨论!!!
知识图谱 :
1.代码随想录
2.acwing
-
01背包问题 : N个物品V空间容量,每个物品仅能用一次,v[i],w[i],是总价值最大
-
完全背包问题 : 每件物品有无数个
-
多重背包问题 : 每个物品si个;
-
分组背包 : M组
关于dp问题 :
-
先考虑状态表示 : 如f(i,j),又可以分为集合和属性 :
-
集合 : 如在01背包问题中,f(i,j)表示只从前i个物品中选且满足总体积<=j的所有选法;
-
属性 : 本题求得是最大值,也就是max,其它得还有min,数量等;
-
-
再考虑状态计算,这一部分也就是考虑状态转移方程式,即f(i,j)能由那个状态得来;
-
对应集合的划分,表示能分成的子集的集合;
-
在01背包问题中,f(i,j)可以划分为含i和不含i的两种情况;
-
0-1背包问题
定义 :
给定一个容量为V的背包,现在有N个物品,每个物品的价值为vi,重量为wi,求选择一个或多个,再不超过容量的情况下,求每个物品最多选一次的前提下的最大价值;
例题 :
-
2. 01背包问题 - AcWing题库
-
[NOIP2005 普及组] 采药 - 洛谷
题面 :
思路 :
重要变量&公式解释 f(i,j):表示所有选法集合中,只从前i个物品中选,并且总体积 ≤ j的选法的集合;它的值是这个集合中每一个选法的最大值.
状态转移方程 f [i] [j] = max(f(i-1,j), f(i-1,j-v[i])+w[i])
f(i-1 , j) : 不选第i个物品的集合中的最大值 f(i-1,j-v[i]) + w[i] : 选第i个物品的集合,但是直接求不容易求所在集合的属性,这里迂回打击一下,先将第i个物品的体积减去,求剩下集合中选法的最大值.
代码(二维) :
#include<iostream>
using namespace std;
const int N = 1010;
int v[N],w[N];
int dp[N][N];
int main(){int n,V;cin>>n>>V;for(int i=1;i<=n;i++) cin>>v[i]>>w[i];for(int i=1;i<=n;i++){for(int j=1;j<=V;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]);}}cout << dp[n][V] << endl;return 0;
}
代码(一维优化版):
#include<iostream>
using namespace std;
const int N = 1010;
int v[N],w[N];
int dp[N];
int main(){int n,m;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] << endl;return 0;
}
完全背包问题
题目链接 :
3. 完全背包问题 - AcWing题库
题面 :
思路 :
f(i,j)表示在前i个物品中选,且总体积不大于j的所有选法
所以状态转移方程式 :
f(i , j) = f( i-1,j-v[i]*k) + w[i] * k
然后根据思路可以模拟出以下的朴素代码 (会超时):
代码(朴素) :
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1005;
int n,m;
int v[N],w[N];
int f[N][N];
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++){for(int k=0;k*v[i]<=j;k++){f[i][j] = max(f[i][j] , f[i-1][j-v[i]*k]+w[i]*k);}}}cout << f[n][m] << endl;return 0;
}
代码(优化) :
上面代码有可能会超时( O(n^3) )
优化思路 :
f[i , j ] = max( f[i-1,j] , f[i-1,j-v]+w , f[i-1,j-2*v]+2*w , f[i-1,j-3*v]+3*w , .....) f[i , j-v]= max( f[i-1,j-v] , f[i-1,j-2*v] + w , f[i-1,j-3*v]+2*w , .....) 由上两式,可得出如下递推关系: f[i][j]=max(f[i,j-v]+w , f[i-1][j])
然后根据 :
f[i][j] = max(f[i][j-v]+w,f[i-1][j]);
核心循环代码可以优化为 :
for(int i = 1 ; i <=n ;i++) for(int j = 0 ; j <=m ;j++) {f[i][j] = f[i-1][j];if(j-v[i]>=0)f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]); }
然后整个代码可以表示为:
#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N = 1005; int n,m; int v[N],w[N]; int f[N][N]; 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++){f[i][j] = f[i-1][j];if(j>=v[i]) f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);}}cout << f[n][m] << endl;return 0; }
代码(一维优化) :
看前一步优化完的代码,先于01背包进行比较 :
f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);//01背包 f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);//完全背包问题
所以相同的进行一维优化 :
for(int i = 1 ; i<=n ;i++)for(int j = v[i] ; j<=m ;j++)//注意了,这里的j是从小到大枚举,和01背包不一样{f[j] = max(f[j],f[j-v[i]]+w[i]);}
优化完的整体代码 :
#include<iostream> using namespace std; const int N = 1010; int f[N]; int v[N],w[N]; int main() {int n,m;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++){f[j] = max(f[j],f[j-v[i]]+w[i]);}cout<<f[m]<<endl; }
多重背包问题
题目链接 :
4. 多重背包问题 I - AcWing题库
题面 :
思考 :
dp :
状态表示 :
-
集合 : 说有只从前i个物品中选,总体积不超过j的选法 , 表示成f(i,j)
-
属性 : max
状态计算 :
-
f(i,j)的集合划分 : 根据第i个物品选多少个,最多选s[i]个,可划分为s[i]+1个集合;
-
那么就可以确定状态转移方程式了:
f[i][j] = max(f[i][j] , f[i-1][j-v[i]*k]+w[i]*k); k = 0,1,2...s[i];
代码 (朴素):
#include<iostream> #include<cstring> #include<algorithm> using namespace std; typedef long long LL; const int N = 105; int n,m; int v[N],w[N],s[N]; int f[N][N]; int main(){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 j=0;j<=m;j++){for(int k=0;k<=s[i]&&k*v[i]<=j;k++){f[i][j] = max(f[i][j] , f[i-1][j-v[i]*k]+w[i]*k);}}}cout << f[n][m] << endl;return 0; }
多重背包问题II
链接 :
-
5. 多重背包问题 II - AcWing题库
-
Problem - 2844
思路 :
其它均与上一题相同,不过数据范围扩大了,会超时,那么就要考虑优化的问题;
优化 (这里采用二进制优化的方法) :
假定给出第i组的物品数量为s[i],也就是s,那么s可以表示为,1,2,4,8,... 2^k,c的和;
由1,2,4,8,... 2^k,c组合可以表示任意[1,s]之间的数;一个log(s)个
例如200可以根据上式表示为 :
1 2 4 8 16 32 64 63
这样分之后,也就是对这每一组数据的分类形成的集合做一个01背包问题,可以把朴素代码的时间复杂度O(N * V * S) 优化为 O(N * V * log(S));
可能这会理解不了,请看详细的二进制优化详解 : 背包问题的二进制优化_二进制优化很神奇_桐小目的博客-CSDN博客
代码(二进制优化) :
#include<iostream> #include<cstring> #include<algorithm> using namespace std; typedef long long LL; const int N = 25000; int n,m; int v[N],w[N],s[N]; int f[N]; int main(){cin>>n>>m;int cnt = 0;for(int i=1;i<=n;i++){int a,b,s;cin>>a>>b>>s;int k = 1;while(k<=s){cnt++;v[cnt] = a * k;w[cnt] = b * k;s -= k;k *= 2;}if(s>0){cnt ++;v[cnt] = a * s;w[cnt] = b * s;}}n = cnt;for(int i=1;i<=n;i++)for(int j=m;j>=v[i];j--){f[j] = max(f[j],f[j-v[i]]+w[i]);}cout << f[m] << endl;return 0; }
分组背包问题
题目链接 :
9. 分组背包问题 - AcWing题库
思考 :
-
集合表示 : f(i,j) 还是 表示 只从前i组物品中选,且总体积不大于j的所有方案数;
-
属性 : max
-
状态计算 : 思想与前几题相类似,f(i,j)划分为不从第i组中选物品和从第i组中选第k个物品两种情况;
-
状态转移方程式 :
f[i][j] = max(f[i-1][j] , f[i-1][j-v[i,k]]+w[i][k]);
那么就可以轻松得到以下代码 :
代码 (朴素):
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 105; int n,m; int v[N][N],w[N][N],s[N]; int f[N][N]; int main() {cin>>n>>m;for(int i=1;i<=n;i++){cin>>s[i];for(int j=0;j<s[i];j++)cin>>v[i][j]>>w[i][j];}for(int i=1;i<=n;i++)for(int j=0;j<=m;j++){f[i][j] = f[i-1][j];for(int k=0;k<s[i];k++){if(j>=v[i][k])f[i][j] = max(f[i][j] , f[i-1][j-v[i][k]]+w[i][k]);}}cout << f[n][m] << endl;return 0; }
代码(一维优化) :
因为只用到了第i-1列,所以可以仿照01背包的套路逆向枚举体积
#include<bits/stdc++.h> using namespace std; const int N=110; int f[N]; int v[N][N],w[N][N],s[N]; int n,m,k; int main(){cin>>n>>m;for(int i=0;i<n;i++){cin>>s[i];for(int j=0;j<s[i];j++){cin>>v[i][j]>>w[i][j];}} for(int i=0;i<n;i++){for(int j=m;j>=0;j--){for(int k=0;k<s[i];k++){ //for(int k=s[i];k>=1;k--)也可以if(j>=v[i][k]) f[j]=max(f[j],f[j-v[i][k]]+w[i][k]); }}}cout<<f[m]<<endl; }