[Acwing]1371. 货币系统
原创
©著作权归作者所有:来自51CTO博客作者LibreCoder的原创作品,请联系作者获取转载授权,否则将追究法律责任
算法标签 完全背包问题
题目简叙
思路
要解决这道题我们需要在01背包问题的基础上拓展完全背包问题
区别在于完全背包问题的是无限可取的,而01背包问题的状态只有取和不取
我们首先复习一下完全背包问题
![在这里插入图片描述 [Acwing]1371. 货币系统_完全背包](https://s2.51cto.com/images/blog/202303/20141816_6417faa851cfb16237.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
完全背包问题思考逻辑
![在这里插入图片描述 [Acwing]1371. 货币系统_完全背包问题_02](https://s2.51cto.com/images/blog/202303/20141816_6417faa86a42f48785.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
整体逻辑
#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](https://s2.51cto.com/images/blog/202303/20141816_6417faa87e0ef54726.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
现在我们优化空间
降低不必要的空间复杂度
这里降低的依据只有一条 就是等价代换
#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](https://s2.51cto.com/images/blog/202303/20141816_6417faa88d50476341.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
有了完全背包问题的概念之后,我们来查看问题 货币系统
![在这里插入图片描述 [Acwing]1371. 货币系统_完全背包_05](https://s2.51cto.com/images/blog/202303/20141816_6417faa89b30e6157.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
这里值得注意的是,我们求得是方案数量,而非最大价值
我们现在朴素思路走一遍
#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](https://s2.51cto.com/images/blog/202303/20141816_6417faa8b25a881681.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
我们现在利用刚才推导出来的优化空间的版本进行解决
#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](https://s2.51cto.com/images/blog/202303/20141816_6417faa8c111a95905.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
我们继续优化一个空间 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](https://s2.51cto.com/images/blog/202303/20141816_6417faa8d2eb098457.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
现在的时间就被压缩到了11MS