算法标签 完全背包问题

题目简叙

思路

要解决这道题我们需要在01背包问题的基础上拓展完全背包问题
区别在于完全背包问题的是无限可取的,而01背包问题的状态只有取和不取

我们首先复习一下完全背包问题

[Acwing]1371. 货币系统_完全背包


完全背包问题思考逻辑

[Acwing]1371. 货币系统_完全背包问题_02


整体逻辑

#include<iostream>

using namespace std;
const int N=1e3+10;
int f[N][N];//状态表示 f[i][j] 表明 前i个物品 体积为j 价值为f[i][j]
int v[N],w[N];//v 重量 w价值

int main()
{
int n,m;//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=0;j<=m;j++)//为什么从0为起始 因为重量可以表示从0到m
for(int k=0;k*v[i]<=j;k++)//选择第i个物品的数量,只要不超出总容量,显然你就可以一直存放
f[i][j]=max(f[i-1][j],f[i][j-k*v[i]]+k*w[i]);//当我们抉择第i个是否选择时,这里对比的是可以选择多少第i个物品,或者不选择,使得自身保持不变

int res=0;
for(int i=1;i<=m;i++)res=max(res,f[n][i]);//在各个选择完数量 但是重量不一致的方案中 挑去价值最高的
cout<<res;
return 0;
}

优化
我们这里需要使用三重循环,复杂度显然太高

我们阐明挑选有多少个第i个物品时,f[i][j]的表示


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-3*v]+2*w, f[i-1][j-4*v]+3*w,...) 通过观察,我们可以得到 f[i][j]=f[i][j-v]+w;


于是我们可以得到新的状态转移方程

for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
if(j>=v[i])
f[i][j]=max(f[i-1][j],f[i][j-v[i]]);
else f[i][j]=f[i-1][j];

我们提交之后发现这样的逻辑是可行的

[Acwing]1371. 货币系统_完全背包问题_03

现在我们优化空间
降低不必要的空间复杂度

这里降低的依据只有一条 就是​​等价代换​

#include<iostream>

using namespace std;
const int N=1e3+10;
int v[N],w[N];
int f[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[i]条件提到循环中 这种情况下更新就只剩一个
f[j]=max(f[j],f[j-v[i]]+w[i]);//降维

cout<<f[m];//一维只有不增,和增,输出最后一个

return 0;
}

我们再次提交,返现可行

[Acwing]1371. 货币系统_i++_04


有了完全背包问题的概念之后,我们来查看问题 货币系统

[Acwing]1371. 货币系统_完全背包_05

这里值得注意的是,我们求得是方案数量,而非最大价值
我们现在朴素思路走一遍

#include<iostream>

using namespace std;
const int N=3e3+10;
typedef long long LL;//存在爆栈 所以我们使用 long long int
LL f[N][N];//DP
LL v[N];//货币面值

int main()
{
int n,m;
cin>>n>>m;//n钟货币,m的面值

f[0][0]=1;//初始化起点的种数为1

for(int i=1;i<=n;i++)cin>>v[i];//输入不同的面值

for(int i=1;i<=n;i++)//考虑前i种
for(int j=0;j<=m;j++)//总的面值为j
for(int k=0;k<=j;k++)//每次取k张面值为v[i]的货币
if(k*v[i]<=j)//如果我们能拿得下
f[i][j]+=f[i-1][j-k*v[i]];//前i种货币且总面值为j的方案数量 累加了 前一种货币时候的所有可能的面值的情况

cout<<f[n][m];//输出数量为n位置为m的方案的数量

return 0;
}

依照这个逻辑,我们发现
当前的状态都是由之前的状态所决定的
我们由第一题的解答所获得的收获可以产生一个疑惑
是不是
1.当前状态由之前的状态所决定
2.拥有记忆化特性
3.拥有搜索特征
的策略即为DP?

现在我们来查看这个方式所耗费的时间

[Acwing]1371. 货币系统_完全背包_06

我们现在利用刚才推导出来的优化空间的版本进行解决

#include<iostream>

using namespace std;
const int N=3e3+10;
typedef long long int LL;
LL v[N];
LL f[N];

int main()
{
int n,m;
cin>>n>>m;

for(int i=1;i<=n;i++)cin>>v[i];

f[0]=1;//初始化为1

for(int i=1;i<=n;i++)
for(int j=v[i];j<=m;j++)
f[j]+=f[j-v[i]];//直接增加

cout<<f[m];

return 0;
}

[Acwing]1371. 货币系统_ci_07

我们继续优化一个空间 v

#include<iostream>

using namespace std;
const int N=3e3+10;
typedef long long int LL;
LL f[N];

int main()
{
int n,m;
cin>>n>>m;

f[0]=1;//初始化为1

int v;
for(int i=1;i<=n;i++)
{
cin>>v;//因为每次都是v[i],我们直接去掉整个v数组,加入临时变量
for(int j=v;j<=m;j++)
f[j]+=f[j-v];
}

cout<<f[m];

return 0;
}

[Acwing]1371. 货币系统_i++_08

现在的时间就被压缩到了11MS