目录
简论
关于dp问题 :
编辑
0-1背包问题
定义 :
例题 :
题面 :
编辑
思路 :
代码(二维) :
代码(一维优化版):
完全背包问题
题目链接 :
题面 :
编辑
思路 :
代码(朴素) :
代码(优化) :
代码(一维优化) :
多重背包问题
题目链接 :
题面 :
编辑
思考 :
代码 (朴素):
多重背包问题II
链接 :
思路 :
代码(二进制优化) :
分组背包问题
题目链接 :
思考 :
代码 (朴素):
代码(一维优化) :
这篇将关于动态规划问题中的背包问题(上),包括,01背包,完全背包,多重背包,分组背包的朴素代码和其优化的详细解答,主要基于acwing算法基础课动态规划讲解,欢迎留言讨论!!!
知识图谱 :
1.代码随想录
2.acwing
01背包问题 : N个物品V空间容量,每个物品仅能用一次,v[i],w[i],是总价值最大
完全背包问题 : 每件物品有无数个
多重背包问题 : 每个物品si个;
分组背包 : M组
先考虑状态表示 : 如f(i,j),又可以分为集合和属性 :
集合 : 如在01背包问题中,f(i,j)表示只从前i个物品中选且满足总体积<=j的所有选法;
属性 : 本题求得是最大值,也就是max,其它得还有min,数量等;
再考虑状态计算,这一部分也就是考虑状态转移方程式,即f(i,j)能由那个状态得来;
对应集合的划分,表示能分成的子集的集合;
在01背包问题中,f(i,j)可以划分为含i和不含i的两种情况;
给定一个容量为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
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
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
#include
#include
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#include #include 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]); }
优化完的整体代码 :
#includeusing 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< 多重背包问题
题目链接 :
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#include #include 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#include #include 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#include #include 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 >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=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背包的套路逆向枚举体积
#includeusing 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 >s[i]; for(int j=0;j >v[i][j]>>w[i][j]; } } for(int i=0;i=0;j--){ for(int k=0;k =1;k--)也可以 if(j>=v[i][k]) f[j]=max(f[j],f[j-v[i][k]]+w[i][k]); } } } cout<