1. 01背包问题
有N件物品和一个容量是V的背包,每件物品只能使用一次,第i件物品的体积是
V
i
V_i
Vi,价值是
W
i
W_i
Wi,求将哪些物品放入背包,可以使这些物品的总体积不超过背包容量,且总价值最大。
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[N][N],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]);//需要满足第i件物品的体积不大于总体积j
}
}
cout << f[n][m];
return 0;
/* 待优化
*/
##
}
2. 完全背包问题
有N件物品和一个容量是V的背包,每件物品可以无限件使用,第i件物品的体积是
V
i
V_i
Vi,价值是
W
i
W_i
Wi,求将哪些物品放入背包,可以使这些物品的总体积不超过背包容量,且总价值最大。
- 集合划分:
f[i][j]
从前i
种货币中选择,以最后一个不同点作为划分依据 对于第i
种货币,可以选择0个、1个、2个····k个
也要满足k*w[i] <= j
,因为要在背包能够装的下的情况下 - 状态计算:
f[i][j] = Max ( f[i-1][j] , f[i-1][j-v[i]] + w[i] , ···· , f[i-1][j-k*v[i]] + k*w[i] )
,解释:f[i-1][j]
表示从前i
种货币中选,第i种货币选择0
个,那么就等同于前i-1
种货币中选择且总价值为j
的方案数;f[i-1][j-w[i]]
表示从前i
种货币中选择,第i
种货币选择1
个,那么就等同于从前i-1
种货币中选择且总价值为j-w[i](因为第i种货币选择了一个,所以总价值要减去1个第i种货币的价值)
;f[i-1][j-k*w[i]]
表示从前i
种货币中选择,第i
种货币选择k
个,那么就等同于从前i-1
种货币中选择且总价值为j-k*w[i](因为第i种货币选择了k个,所以总价值要减去k个第i种货币的价值);
- 优化:
f[i][j] = Max ( f[i-1][j] , f[i-1][j-v[i]] + w[i] , ···· , f[i-1][j-k*v[i]] + k*w[i] )
,
对于f[i][j-v[i]] = Max ( f[i-1][j-v[i]] , f[i-1][j-2*v[i]] + w[i] , ···· , f[i-1][j-k*v[i]] + (k-1) * w[i] )
而f[i][j-v[i]] = Max ( f[i-1][j-v[i]] , f[i-1][j-2*v[i]] + w[i] , ···· , f[i-1][j-k*v[i]] + (k-1) * w[i] )
比Max(f[i-1][j-v[i]] + w[i] , ···· , f[i-1][j-k*v[i]] + k*w[i] ))
整体少个w[i]
则f[i][j] = Max ( f[i-1][j] , f[i][j-v[i]] + w[i] )
#include<bits/stdc++.h>
using namespace std;
const int N =1010;
int f[N][N],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=1;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];
/* 优化
*/
}
3. 多重背包问题
有N件物品和一个容量是V的背包,第i种物品最多有
S
i
S_i
Si件,第i种物品的体积是
V
i
V_i
Vi,价值是
W
i
W_i
Wi,求将哪些物品放入背包,可以使这些物品的总体积不超过背包容量,且总价值最大。
f[i][j] = Max ( f[i-1][j] , f[i-1][j-v[i]] + w[i] , ···· , f[i-1][j-k*v[i]] + k*w[i] ) ----k的取值范围:1、2、···、S[i]
朴素做法:O(
n
3
n^3
n3)
#include<bits/stdc++.h>
using namespace std;
const int N = 110 ;
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=1;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];
}
优化做法:
对于f[i][j] = Max ( f[i-1][j] , f[i-1][j-v[i]] + w[i] , ···· , f[i-1][j-k*v[i]] + k*w[i] ) ----k的取值范围:1、2、···、S[i]
比如第i个物品的个数是
S
i
S_i
Si=1023,也就是说对于第i个物品,可以选择0件、1件、···、1023件。那么可以将这1023件分为10组,第1组1件,第2组2件,第3组4件,依次按照2的幂分,第10组是
2
9
2^9
29=512件,这10组可以进行任意搭配(选用每组的个数最多是1)能够凑出来0~1023件,
- 2 n 2^n 2n=2n-1+2n-1,2n-1=2n-2+2n-2,那么 2 n 2^n 2n=2n-1+2n-2+2n-2,那么 2 n 2^n 2n=2n-1+2n-2+2n-3+····+21+21 ,那2n-1-1 = 2n-1+2n-2+2n-3+····+21+20
- 对于一般的S[i]: 2k<S,2k+1>S,那么2k+c = S (c<2k),那么1、2、4、···、2k、c就可以把0~S都凑出来 (每种只限最多选择一次)
比如S=200,26=64<200,27>200,那么可以用1、2、4、8、16、32、64、73凑出0~200,因为1、2、4、···、64可以凑出0 ~ 127,那么再加73就可以凑出73 ~200,那么1、2、4、8、16、32、64、73就可以凑出0 ~200;
#include<bits/stdc++.h>
using namespace std;
const int N = 25000,M = 2010;
int n,m;
int v[N],w[N],s[N];
int f[N];
int main(){
cin >> n >> m;
int res = 0;
for(int i=1;i<=n;i++){
int k = 1;
int a,b,s;
cin >> a>>b>>s;
while(k<=s){
res ++;
v[res] = a*k;
w[res] = b*k;
s -= k;
k *= 2;
}
if(s>0){
res ++;
v[res] = a*s;
w[res] = b*s;
}
}
n = res;
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];
}
4.分组背包问题
有 N 组物品和一个容量是 V 的背包。每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
其中v[i,k] 代表第i组中的k件物品的体积 , w[i,k] 代表第i组中的k件物品的价值
#include<bits/stdc++.h>
using namespace std;
const int N = 110;
int v[N][N],w[N][N];
int f[N],s[N];
int n,m;
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 = m;j>=0;j--){
for(int k = 0;k<s[i];k++){
if(v[i][k]<=j) f[j] = max(f[j] , f[j-v[i][k]] + w[i][k]);
}
}
}
cout << f[m];
}