基本思考框架
状态的划分依然是
但是对于第 [公式] 件物品来说,选择的情况有不选,选一件,…,选k件,…
状态转移方程为:
对于物品数量一层循环,体积一层循环,选几件还有一层循环,时间上是 的复杂度。可以选择如此的朴素的做法,但是不够优雅(狗头)。
但是针对该时间复杂度可以优化至
优化思路!
我们列举一下更新次序的内部关系:
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 , .....)
f[i , j-v]= max( f[i-1,j-v] , f[i-1,j-2*v] + w , f[i-1,j-2*v]+2*w , .....)
由上两式,可得出如下递推关系:
f[i][j]=max(f[i,j-v]+w , f[i-1][j])
有了上面的关系,那么其实k循环可以不要了,核心代码优化成这样:
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]>=0)
f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
}
这个代码和01背包的非优化写法很像啊!!!我们对比一下,下面是01背包的核心代码
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]>=0)
f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
两个代码其实只有一句不同(注意下标)
f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);//01背包
f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);//完全背包问题
进一步优化
同样可以做空间上的优化,这里f[i][j] 除了用到 f[i-1][j] 的状态还用到了 f[i][j-v[i]]+w[i] 的状态,更新 f[i][j] 的时候需要先更新本行前的数据,故体积从小到达循环。
我们来看一组01背包问题和完全背包问题的空间优化代码处理对比
01背包问题问题
优化前
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]>=0)
f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
优化后
for(int i=1;i<=n;i++)
{
for(int j=m;j>=v[i];j--)
{
f[j]=max(f[j-v[i]]+w[i],f[j]);
}
}
完全背包问题
空间优化前
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]);
}
}
空间优化后
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]);
}
}
经过时空二重优化后的代码
#include<iostream>
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>>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]);//因为是j从小到大枚举,
//所以我们在用到之前的j的时候(指j=j-v[i]时)f[j-v[i]]已经被更新过了,所以正好也是我们之前
//想要的第i轮的f[i][j-v[i]]+w[i]
}
cout<<f[m]<<endl;
}