背包问题
核心思想
(1)01背包问题(物品只有1个)
二维数组
状态转移f[i][j] = max(f[i - 1][j] , f[i - 1][j - w[i]] + v[i])
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N][N];
int w[N] , v[N];
int n , m;
int main()
{
int n , m;
cin >> n >> m;
for(int i = 1 ; i <= n ; i ++) cin >> w[i] >> v[i];
for(int i = 1 ; i <= n ; i ++)
for(int j = 0 ; j <= m ; j++)
{
f[i][j] = f[i - 1][j];//继承上一层i的价值
if(j >= w[i]) f[i][j] = max(f[i][j] , f[i - 1][j - w[i]] + v[i]);//当背包可以装下的时候,取较大值
}
cout << f[n][m] << endl;
return 0;
}
一维数组+滚动数组
最终版
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N];
int w[N] , v[N];
int n , m;
int main()
{
int n , m;
cin >> n >> m;
for(int i = 1 ; i <= n ; i ++) cin >> w[i] >> v[i];
for(int i = 1 ; i <= n ; i ++)
for(int j = m ; j >= w[i] ; j--) //因为每层i只用到i-1层的数据而用不到i-2层的,
//因此可以利用滚动数组,此时应倒着遍历j
f[j] = max(f[j] , f[j - w[i]] + v[i]);
cout << f[m] << endl;
return 0;
}
(2)完全背包(物品有无数个)
(转自y总)
暴力算法
状态转移f[i][j] = max(f[i][j] , f[i - 1][j - k * w[i]] + k * v[i])
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
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 >> w[i] >> v[i];
for(int i = 1 ; i <= n ; i++)
for(int j = 0 ; j <= m ; j++)
for(int k = 0 ; k * w[i] <= j ; k++)//暴力判断每个物品的件数
f[i][j] = max(f[i][j] , f[i - 1][j - k * w[i]] + k * v[i]);
cout <<f[m] << endl;
return 0;
}
时间优化:省去k循环
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N][N];
int v[N], w[N];
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> w[i] >> v[i];
for (int i = 1; i <= n; i++)
for (int j = 0; j <= m; j++)
{
f[i][j] = f[i - 1][j];
if (j >= w[i]) f[i][j] = max(f[i][j], f[i][j - w[i]] + v[i]);//因为物品数量无限,所以不用i-1层,只用更新
//当前i层的价值即可
}
cout << f[n][m] << endl;
return 0;
}
进一步优化:空间优化,较少一个维度
最终版
#include <iostream>
#include <algorithm>
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 >> w[i] >> v[i];
for (int i = 1; i <= n; i++)
for (int j = w[i] ; j <= m; j++)
f[j] = max(f[j], f[j - w[i]] + v[i]); //原理类似01背包,但是完全背包需要正序遍历j,因为物品件数无限,
//需要对刚刚更新过的数据继续更新,而非利用i-1层的数据
cout << f[m] << endl;
return 0;
}
下面是01背包的最终版,可以发现,区别仅在j的循环次序,而01背包和完全背包区别仅在1和∞。所以仅此区别也没什么奇怪
01背包的物品只有1个,因此需要逆序遍历j,这样就可以取到i-1层的f[j],不会重复使用当前的物品。
完全背包的物品有无限个,因此正序遍历时,取到的是当前i层的f[j],即已经被当前物品更新过的价值。
(3)多重背包(此时物品的个数不一定)
暴力算法O(nms)
状态转移f[i,j] = max(f[i - 1 , j - k * w]+k * v) (这里没有写错 , 遍历每种物品k次,取最大,k是物品的个数)
枚举每种物品的件数
#include <iostream>
using namespace std;
const int N = 110;
int f[N][N];
int n , m;
int w[N] , v[N] , s[N];
int main()
{
cin >> n >> m;
for(int i = 1 ; i <= n ; i++) cin >> w[i] >> v[i] >> s[i];
for(int i = 1 ; i <= n ; i++)
for(int j = 0 ; j <= m ; j++)
for(int k = 0 ; k <= s[i] && j >= k * w[i] ; k++)
f[i][j] = max(f[i][j] , f[i - 1][j - k * w[i]] + k * v[i]);
cout << f[n][m] << endl;
return 0;
}
--------------------------------------------------------------------------------------------
#include <iostream>//同样可以空间优化,不过时间复杂度相同
using namespace std;
const int N = 110;
int f[N];
int n , m;
int w[N] , v[N] , s[N];
int main()
{
cin >> n >> m;
for(int i = 1 ; i <= n ; i++) cin >> w[i] >> v[i] >> s[i];
for(int i = 1 ; i <= n ; i++)
for(int j = m ; j >= w[i] ; j--)
for(int k = 0 ; k <= s[i] && j >= k * w[i] ; k++)
f[j] = max(f[j] , f[j - k * w[i]] + k * v[i]);
cout << f[m] << endl;
return 0;
}
二进制优化O(nmlogs )
算法核心:将一个s拆分成如1,、2、4、8…2k、c(其中c<2k+1,且该数列的和是s),进而问题转换成一个01背包问题
#include <iostream>
using namespace std;
const int N = 25000;
int f[N];
int n , m;
int cnt;
int w[N] , v[N];
int main()
{
cin >> n >> m;
while(n--)
{
int a , b , s;
cin >> a >> b >> s;
int k = 1 ;
while(k <= s)//将s转换成一个除最后一项外的以2位公比的等比数列
{
w[++cnt] = k * a;
v[cnt] = k * b;
s -= k;
k *= 2;
}
if(s)//对最后一项c特判
{
w[++cnt] = s * a;
v[cnt] = s * b;
}
}
for(int i = 1 ; i <= cnt ; i++)
for(int j = m ; j >= w[i] ; j--)
f[j] = max(f[j] , f[j - w[i]] + v[i]);
cout << f[m] << endl;
return 0;
}
(4)分组背包(一组物品只能取一个)
思路类似,只是每组中只取一个,遍历即可
状态转移f[j] = max(f[j] , f[j - w[i][k]] + v[i][k]);
#include <iostream>
using namespace std;
const int N = 110;
int f[N];
int w[N][N] , v[N][N] ,s[N];
int n , m;
int main()
{
cin >> n >> m;
for(int i = 1 ;i <= n ; i++)
{
cin >> s[i];
for(int j = 1 ; j <= s[i] ; j++)
cin >> w[i][j] >> v[i][j];
}
for(int i = 1 ; i <= n ; i++)
for(int j = m ; j >= 0 ; j--)
for(int k = 1 ; k <= s[i] ; k++)
if(w[i][k] <= j) //因为物品的遍历在体积之下,所以得判断一下,而不能直接在j循环里进行限制
f[j] = max(f[j] , f[j - w[i][k]] + v[i][k]);
cout << f[m] << endl;
return 0;
}