文章目录
01背包问题(划分依据:选不选第i个物品)
题目描述
朴素写法,时间复杂度:O(NV)
#include <iostream>
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 >> 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]; // 不选第i个物品
if(j >= v[i]) f[i][j] = max(f[i][j],f[i-1][j-v[i]] + w[i]); // 选第i个物品
}
cout<<f[n][m]<<endl;
return 0;
}
优化空间后的写法
#include <iostream>
using namespace std;
const int N = 1010;
int n,m;
int f[N];
int main()
{
cin >> n >> m;
for(int i=1;i<=n;i++)
{
int v,w;
cin>>v>>w;
for(int j=m;j>=v;j--) f[j] = max(f[j],f[j-v]+w); // 体积从大到小循环
}
cout<<f[m]<<endl;
return 0;
}
优化空间我的理解
由图中可以看出,新状态(红色标出)要么从上面继承下来(不选第i个物品)或者从体积够的地方转移过来(选第i个物品),它们都是上一层的状态,或者利用状态转移方程
f[i][j] = max(f[i][j],f[i-1][j-v[i]] + w[i])
可以看出第i
层是利用i-1
层空间更新的,利用滚动数组
即可优化一维空间。(边读边更新)
但是优化空间为什么是体积从大到小循环呢?
先看状态转移方程f[j] = max(f[j],f[j-v]+w)
,比如更新i=4
的时候f[4]
用到了f[2]
,此时f[4]
表示的是f[i][4]
,但是后面更新f[6]
的时候又用到f[4]
,但是此时的f[4]
是f[i][4]
,而不是我们想要的f[i-1][4]
。而体积从大到小循环不会出现此类问题,保证更新后的值都不会用到!
简记为:
i-1
层优化空间体积都是从大到小循环,除完全背包
外(因为完全背包的优化是公式替换)。下文不再阐述。
完全背包问题(划分依据:第i个物品选多少个)
题目描述
朴素写法,时间复杂度: O ( N M 2 ) O(NM^2) O(NM2)
#include <iostream>
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 >> v[i] >> w[i];
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
for(int k =0;k*v[i]<=j;k++)
f[i][j] = max(f[i][j],f[i-1][j-k * v[i]] + k * w[i]);
cout<<f[n][m]<<endl;
return 0;
}
优化,时间复杂度O(NM)
这里的系数
k * v <= j
,与多重背包的系数区别。
#include <iostream>
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 >> 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][j],f[i][j-v[i]] + w[i]); // 用到的是第i层的状态
}
cout<<f[n][m]<<endl;
return 0;
}
优化空间的写法,利用滚动数组去掉一维
#include <iostream>
using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N];
int f[N];
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=0;j<=m;j++) // 完全背包体积从小到大循环
{
if(j >= v[i]) f[j] = max(f[j],f[j-v[i]] + w[i]); // 用到的是第i层的状态
}
cout<<f[m]<<endl;
return 0;
}
总结
01背包
#include <iostream>
using namespace std;
int f[1010];
int main()
{
int n,m;
cin >> n >> m;
for(int i=1;i<=n;i++)
{
int v,w;
cin>>v>>w;
for(int j=m;j>=v;j--) f[j]=max(f[j],f[j-v] + w);
}
cout<<f[m];
}
完全背包
#include <iostream>
using namespace std;
int f[1010];
int main()
{
int n,m;
cin >> n >> m;
for(int i=1;i<=n;i++)
{
int v,w;
cin>>v>>w;
for(int j=v;j<=m;j++) f[j]=max(f[j],f[j-v] + w);
}
cout<<f[m];
}
01背包和完全背包只有体积循环方式不同,但完全背包的优化是通过了一层等价变换得到的!
第i-1
层体积从大到小循环(绝大部分),第i
层从小到大。