背包问题
背包问题
01背包问题
问题描述:
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
数据范围:0<N,V≤1000,0<vi,wi≤1000
解析:
DP问题从两个方面考虑
状态表示:f(i,j),需要几维来表示状态,每个状态含义
集合:选法的集合;
集合的条件——只从前i个物品中选,总体积<=j
属性:最大值(背包问题中的属性就是最大值);最小值;数量
状态计算:如何一步步把每一个状态算出来
集合的划分:
不重不漏的分为两类:选法中包含i物品/不包含i物品
1.体积不超过j,前i个物品中不包含i:f(i-1,j)
2.体积不超过j,前i个物品中包含i,f(i-1,j-$v_i$)+$w_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 34 35 36
| #include<iostream> #include<cstdio> using namespace std; const int N=1020; int f[N][N]; int v[N],w[N]; int n,m; 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=1;j<=m;j++) { f[i][j]=f[i-1][j]; if(j>=v[i])f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]); } } cout<<f[n][m]; return 0; }
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]); } }
|
完全背包问题
问题描述:
有 N 件物品和一个容量是 V 的背包。每种物品都有无限件可用。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
数据范围:0<N,V≤1000,0<vi,wi≤1000
解析:完全背包问题与01背包很像,状态表示与01背包相同,而集合的划分与01背包有所区别,不再是刚刚好不重不漏的分为两组,而要考虑第i个物品选k个。
选0个(不选):f(i-1,j)
选k个:f(i-1,j-k*v[i])+w[i]*
k
朴素做法代码:
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
| #include<iostream> #include<cstdio> using namespace std; const int N=1020; int f[N][N]; int v[N],w[N]; int n,m; 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-k*v[i]]+k*w[i]); } } } cout<<f[n][m]; return 0; }
|
考虑优化,分析状态转移方程:
f(i,j)=max{ f(i-1,j) , f(i-1,j-v)+w , f(i-1,j-2v)+2w,…}
f(i,j-v)=max{ f(i-1,j-v) , f(i-1,j-2v)+w , f(i-1,j-3v)+2w,…}
注意到f(i,j)的很多状态和f(i,j-v)的相同,因此可以很容易发现不需要枚举k,状态转移方程改为:
f(i,j)=max{ f(i-1,j), f(i,j-v)+w }
优化后代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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]); } }
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]); } }
|
多重背包问题
问题描述:
有 N 件物品和一个容量是 V 的背包。
第 i 件物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
数据范围:0<N,V≤1000,0<vi,wi,si≤100
解析:朴素版参考完全背包处的分析
代码:
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
| #include<iostream> #include<cstdio> using namespace std; const int N=1020; int f[N][N]; int v[N],w[N],s[N]; int n,m; 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] and k*v[i]<=j;k++) { f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]); } } } cout<<f[n][m]; return 0; }
|
题目不变,更新数据范围
0<N≤1000,0<V≤2000,0<vi,wi,si≤2000
数据强化后,n^3显然会超时。如何优化?
考虑优化,分析状态转移方程:
f(i,j)=max{ f(i-1,j) , f(i-1,j-v)+w,… , f(i-1,j-sv)+sw}
f(i,j-v)=max{ f(i-1,j-v) , f(i-1,j-2v)+w , …,f(i-1,j-sv)+(s-1)w, f(i-1,j-(s+1)v)+sw }
发现f(i,j-v)除了多了最后一项之外都和f(i,j)一样,那么如何求除去最后一项的f(i,j-v)的最大值呢?似乎有点难…
二进制优化:把每种物品打包成:1,2,4,8,…,$2^n$个为一组($2^n$<=s[i],剩下的拿s[i]-$(2^n-1)$=c作为最后一组)s个物品就拆成了 logs 个物品,再对这logs个做01背包即可。这logs个物品选下来将会包含s个物品的所有选法!
代码:
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 36 37 38 39
| #include<iostream> #include<cstdio> using namespace std; const int N=25000,M=2010; int n,m; int v[N],w[N],f[N]; int cnt=0; int main() { cin>>n>>m; for(int i=1;i<=n;i++) { int a,b,s; cin>>a>>b>>s; int k=1; while(k<=s) { v[++cnt]=a*k; w[cnt]=b*k; s-=k; k*=2; } if(s>0) { 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]; return 0; }
|
分组背包问题
有 N 组物品和一个容量是 V 的背包。
每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 v_ij,价值是 w_ij,其中 i 是组号,j 是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
0<N,V≤100, 0<Si≤100, 0<vij,wij≤100
解析:
状态表示:
集合:只从前i组物品中选,总体积不大于j的所有选法
属性:选法中总价值的最大值
状态计算:完全背包/多重背包:第i组物品选几个
分组背包:第i组物品选哪个——只有一个选(联想到01背包)
不选:f(i-1,j) 选第k个:f(i-1,j-v[i,k])+w[i,k];
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| #include<iostream> #include<cstdio> using namespace std; const int N=150; int n,m; int v[N][N],w[N][N]; int s[N],f[N][N]; int main() { ios::sync_with_stdio(0); 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=0;j<=m;j++) { f[i][j]=f[i-1][j]; for(int k=1;k<=s[i];k++) { if(v[i][k]<=j) { f[i][j]=max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]); } } } } cout<<f[n][m]; return 0; }
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) { f[j]=max(f[j],f[j-v[i][k]]+w[i][k]); } } } }
|