动态规划之背包问题
01背包
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8
枚举:我们可以用0和1来表示当前物品是不是取了,0表示没有取,1表示取了,比如说有三个物品,001,那么001表示第一个物品取了,第二个和第三个物品都没有取
当我们考虑了前i个物品取还是不取的情况,如果有两组方案的体积是一样的,保留最大收益的即可。
考虑了前i个物品,总体积为j的情况分为两种:
1.第i
个物品没有取,就是考虑前i-1
体积为j
的情况
2.第i
个物品取了,就是考虑前i-1
体积为j-v[i]
的情况
最有子结构:为了计算前i
个物品且体积为j
的情况下收益的最大值,我们可以先考虑上一个状态i-1
的最大值,前i-1
个物品的状态也分为两个状态体积为j
和j-v[i]
的最大收益。
无后效性:之考虑最值,不考虑是拿了哪些物品而得到的最大值
状态:f[i][j]
表示考虑拿前i
个物品,总体积为j
的最大收益
转移:f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i])
AC代码:
#include<iostream>
using namespace std;
const int N = 1010;
int v[N],w[N], f[N][N],n,m;
int main(void)
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
scanf("%d %d", &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 - 1][j], f[i - 1][j - v[i]] + w[i]);
}
}
}
cout << f[n][m] << endl;
return 0;
}
思考:
真的需要开这么大的数组吗?有没有可以优化的地方
考虑前i
个物品时的状态之和考虑了前i-1
个物品的状态有关,前面的i-2
是不需要被记录的。
f[1][1]=2 |
f[0][1]=0 |
f[0][0]+w[1]=2 |
---|---|---|
f[1][2]=2 |
f[0][2]=0 |
f[0][1]+w[1]=2 |
f[1][3]=2 |
f[0][3]=0 |
f[0][3]+w[1]=2 |
f[1][4]=2 |
f[0][4]=0 |
f[0][3]+w[1]=2 |
f[1][5]=2 |
f[0][5]=0 |
f[0][4]+w[1]=2 |
f[2][2]=4 |
f[1][2]=2 |
f[1][0]+w[2]=4 |
f[2][3]=6 |
f[1][3]=2 |
f[1][1]+w[2]=6 |
f[2][4]=6 |
f[1][4]=2 |
f[1][2]+w[2]=6 |
f[2][5]=6 |
f[1][5]=2 |
f[1][3]+w[2]=6 |
f[3][3]=6 |
f[2][3]=6 |
f[2][0]+w[3]=4 |
数据跟踪代码:
#include<iostream>
using namespace std;
const int N = 1010;
int v[N],w[N], f[N][N],n,m;
int main(void)
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
scanf("%d %d", &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 - 1][j], f[i - 1][j - v[i]] + w[i]);
printf("f[%d][%d]= %d f[%d][%d]= %d f[%d][%d]+w[%d]= %d\n", i, j, f[i][j], i - 1, j, f[i - 1][j], i - 1, j - v[i], i, f[i - 1][j - v[i]] + w[i]);
}
}
}
cout << f[n][m] << endl;
return 0;
}
优化后的代码:
#include<iostream>
using namespace std;
const int N = 1010;
int v[N],w[N], f[N],n,m;
int main(void)
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
scanf("%d %d", &v[i], &w[i]);
}
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]);
}
puts("");
}
cout << f[m] << endl;
return 0;
}
我们来看一下二维的转移方程
f[i][j]=f[i-1][j];//没有取第i个物品
f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);//取了第i个物品
f[i][j]=f[i-1][j]可以等效于f[j]=f[j],这个式子是恒成立的,所以可以直接不写
f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);可以等效于:
f[j]=max(f[j],f[j-v[i]]+w[i]);但如何去得到i-1和i轮的数据呢?
我们可以把枚举体积的状态逆向更新ÿ