背包问题
1. 01背包问题
View Code
View Code
View Code
View Code
View Code
View Code
二维表示
对于01背包一维优化的一点理解:
二维转化为一维:
删掉了第一维:在前i个物品中取。
f[j]表示:拿了总体积不超过j的物品,最大总价值。
为何能转化为一维?
二维时的更新方式:f[i][j]=max(f[i - 1][j] ,f[i - 1][j - v[i]] + w[i]);
1.我们发现,对于每次循环的下一组i,只会用到i-1来更新当前值,不会用到i-2及之前值。于是可以在这次更新的时候,将原来的更新掉,反正以后也用不到。
所以对于i的更新,只需用一个数组,直接覆盖就行了。
2.我们发现,对于每次j的更新,只需用到之前i-1时的j或者j-v[i],不会用到后面的值。
所以为了防止串着改,我们采取从后往前更新的方式,用原来i-1的数组来更新i。
(如果从前往后更新的话,前面的更新过之后,会接着更新后面的值,这样就不能保证是用原来i-1的数组来更新i的了)
如何转化为一维呢?
只用一个数组,每次都覆盖前面的数组。
1.如果当前位置的东西不拿的话,和前一位置的信息(原来i-1数组的这个位置上的值)是相同的,所以不用改变。
2.如果当前位置的东西拿了的话,需要和前一位置的信息(原来i-1数组的这个位置上值)取max。
所以,更新方式就为:f[j]=max(f[j],f[j-v[i]]+w[i]);
整个更新方式就相当于:
每次i++,就从后往前覆盖一遍f数组,看每个位置上的值是否更新

1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 1010; 5 int n,m; //个数和背包容量 6 int v[N],w[N]; //每个物品的体积和价值 7 int f[N][N]; //表示状态 8 9 int main() 10 { 11 cin >> n >> m; 12 13 for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i]; //读入所有的物品 14 15 // f[0][0 ~ m] = 0; //0个没有意义,且全局变量已经初始化 16 17 //f[i][j] 表示从1~i的数,总价值不超过j的集合 18 for (int i = 1; i <= n; i ++ ) //枚举个数 19 for (int j = 0; j <= m; j ++ ) //枚举体积 20 { 21 f[i][j] = f[i - 1][j]; 22 if(j >= v[i]) f[i][j] = max(f[i - 1][j],f[i - 1][j - v[i]] + w[i]); //判断是否为空集 23 } 24 cout << f[n][m]; 25 26 return 0; 27 }
一维滚动数组表示

1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 1010; 5 int n,m; //个数和背包容量 6 int v[N],w[N]; //每个物品的体积和价值 7 int f[N]; //表示状态 8 9 int main() 10 { 11 cin >> n >> m; 12 13 for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i]; //读入所有的物品 14 15 // f[0][0 ~ m] = 0; //0个没有意义,且全局变量已经初始化 16 17 //f[i][j] 表示从1~i的数,总价值不超过j的集合 18 for (int i = 1; i <= n; i ++ ) //枚举个数 19 for (int j = m; j >= v[i]; j -- ) //枚举体积 20 { 21 //f[j] = f[j],去掉了f[i]那一层 22 //要使得j >=v[i]才有结果,所以可以让循环从j = v[i]开始 23 //但由于j - v[i] < v[i],所以在这第i层循环中已经被遍历过了 24 //等价于 f[i][j] = max(f[i - 1][j],f[i][j - v[i]] + w[i]),但正确的是f[i - 1]; 25 //所以将其反过来遍历一次,此时 j -v[i] > v[i],没有在之前被遍历过 26 //if(j >= v[i]) f[i][j] = max(f[i - 1][j],f[i - 1][j - v[i]] + w[i]); 27 f[j] = max(f[j],f[j - v[i]] + w[i]); 28 } 29 cout << f[m] << endl; 30 31 return 0; 32 }
2.完全背包问题
详情请看代码

1 #include<iostream> 2 using namespace std; 3 const int N = 1010; 4 int f[N][N]; 5 int v[N],w[N]; 6 int main() 7 { 8 int n,m; 9 cin>>n>>m; 10 for(int i = 1 ; i <= n ;i ++) 11 { 12 cin>>v[i]>>w[i]; 13 } 14 15 for(int i = 1 ; i <= n ;i ++) 16 for(int j = 0 ; j <= m ;j ++) 17 { 18 for(int k = 0 ; k*v[i] <= j ; k ++) 19 f[i][j] = max(f[i][j],f[i - 1][j - k * v[i]] + k * w[i]); 20 } 21 22 cout<<f[n][m]<<endl; 23 } 24 25 //优化思路(列举一下更新次序的内部关系) 26 /* 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 , .....) 27 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 , .....) 28 由上两式,可得出如下递推关系: 29 f[i][j]=max(f[i,j-v]+w , f[i-1][j]) */ 30 31 //有上述关系,k循环则可以不要,核心代码简化 32 /*for(int i = 1 ; i <=n ;i++) 33 for(int j = 0 ; j <=m ;j++) 34 { 35 f[i][j] = f[i-1][j]; 36 if(j-v[i]>=0) 37 f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]); 38 } */ 39 40 //对比01背包核心问题 41 /*for(int i = 1 ; i <= n ; i++) 42 for(int j = 0 ; j <= m ; j ++) 43 { 44 f[i][j] = f[i-1][j]; 45 if(j-v[i]>=0) 46 f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]); 47 } */ 48 49 //代码最终简化 50 /*for(int i = 1 ; i<=n ;i++) 51 for(int j = v[i] ; j<=m ;j++) //注意了,这里的j是从小到大枚举,和01背包不一样 52 { 53 f[j] = max(f[j],f[j-v[i]]+w[i]); 54 } */ 55 56 57 #include<iostream> 58 using namespace std; 59 const int N = 1010; 60 int f[N]; 61 int v[N],w[N]; 62 int main() 63 { 64 int n,m; 65 cin>>n>>m; 66 for(int i = 1 ; i <= n ;i ++) 67 { 68 cin>>v[i]>>w[i]; 69 } 70 71 for(int i = 1 ; i<=n ;i++) 72 for(int j = v[i] ; j<=m ;j++) 73 { 74 f[j] = max(f[j],f[j-v[i]]+w[i]); 75 } 76 cout<<f[m]<<endl; 77 }
3.多重背包问题
<1> 朴素版

1 #include <iostream> 2 #include <algorithm> 3 4 using namespace std; 5 const int N = 110; 6 7 int v[N], w[N], s[N]; 8 int f[N][N]; 9 int n, m; 10 11 int main(){ 12 cin >> n >> m; 13 for(int i = 1; i <= n; i ++) cin >> v[i] >> w[i] >> s[i]; 14 15 for(int i = 1; i <= n; i ++){//枚举背包 16 for(int j = 1; j <= m; j ++){//枚举体积 17 for(int k = 0; k <= s[i] && j >= k * v[i]; k ++){ 18 f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]); 19 } 20 } 21 } 22 23 cout << f[n][m] << endl; 24 25 return 0; 26 }
<2>二进制优化

1 #include<iostream> 2 using namespace std; 3 4 const int N = 12010, M = 2010; 5 6 int n, m; 7 int v[N], w[N]; //逐一枚举最大是N*logS 8 int f[M]; // 体积<M 9 10 int main() 11 { 12 cin >> n >> m; 13 int cnt = 0; //分组的组别 14 for(int i = 1;i <= n;i ++) 15 { 16 int a,b,s; 17 cin >> a >> b >> s; 18 int k = 1; // 组别里面的个数 19 while(k<=s) 20 { 21 cnt ++ ; //组别先增加 22 v[cnt] = a * k ; //整体体积 23 w[cnt] = b * k; // 整体价值 24 s -= k; // s要减小 25 k *= 2; // 组别里的个数增加 26 } 27 //剩余的一组 28 if(s>0) 29 { 30 cnt ++ ; 31 v[cnt] = a*s; 32 w[cnt] = b*s; 33 } 34 } 35 36 n = cnt ; //枚举次数正式由个数变成组别数 37 38 //01背包一维优化 39 for(int i = 1;i <= n ;i ++) 40 for(int j = m ;j >= v[i];j --) 41 f[j] = max(f[j],f[j-v[i]] + w[i]); 42 43 cout << f[m] << endl; 44 return 0; 45 }
4.分组背包问题

1 #include<bits/stdc++.h> 2 using namespace std; 3 4 const int N = 110; 5 6 int n,m; 7 int v[N][N],w[N][N],s[N]; 8 int f[N]; 9 10 11 int main() 12 { 13 cin >> n >> m; 14 15 for (int i = 1; i <= n; i ++ ) //枚举物品组 16 { 17 cin >> s[i]; 18 for (int j = 0; j < s[i]; j ++ ) 19 cin >> v[i][j] >> w[i][j]; 20 } 21 22 23 for (int i = 1; i <= n; i ++ ) //枚举每一组物品 24 for (int j = m; j >= 0; j -- ) 25 for (int k = 0; k < s[i]; k ++ ) //枚举每组物品中的每个选择 26 { 27 if(v[i][k] <= j) 28 f[j] = max(f[j],f[j - v[i][k]] + w[i][k]); 29 } 30 31 cout << f[m] << endl; 32 return 0; 33 }