凑零钱——动态规划0/1背包问题思想

韩梅梅喜欢满宇宙到处逛街。现在她逛到了一家火星店里,发现这家店有个特别的规矩:你可以用任何星球的硬币付钱,但是绝不找零,当然也不能欠债。韩梅梅手边有10000枚来自各个星球的硬币,需要请你帮她盘算一下,是否可能精确凑出要付的款额。
输入格式:输入第一行给出两个正整数:N(<=10000​​)是硬币的总个数,M(<=100​​)是韩梅梅要付的款额。第二行给出 N枚硬币的正整数面值。数字间以空格分隔。
输出格式:在一行中输出硬币的面值 V1≤V2≤⋯≤Vk​​,满足条件 V1+V2+…+Vk=M。数字间以 1 个空格分隔,行首尾不得有多余空格。若解不唯一,则输出最小序列。若无解,则输出 No Solution。
注:我们说序列{ A[1],A[2],⋯⋯ }比{ B[1],B[2],⋯ }“小”,是指存在 k≥1 使得 A[i]=B[i]对所有 i<k 成立,并且 A[k]<B[k]。
输入样例 1:
8 9
5 9 8 7 2 3 4 1
输出样例 1:
1 3 5
输入样例 2:
4 8
7 2 4 3
输出样例 2:
No Solution

首先明确一点,中心思路跟0/1背包问题的思路完全一样。
那么说一下我做的过程吧,首先我当时做的时候有点懵,我明白这是动态规划的思路,但我不知道怎么去解题,0/1背包可以很好的理解,就是判断当前的物品是否能装入背包:①不能——>判断下一个;②能:判断最优解是否包括当前物品,得出满足当前背包容量的最优解。
我觉得背包问题直接理解就是:加入当前的物品(n+1个)的最优解与没加之前(n个)的最优解作比较,二者择其优。
因为我觉得凑零钱问题没有可比较的东西,所以我刚开始毫无头绪,不过明白一点后,就恍然大悟了那就是凑零钱和背包问题都有一个关键的点——每一个钱币(物品)只有两种选择,选或者不选。在这里插入图片描述
如上图,最开始的钱是0,我们从第一个钱币开始选择,每次都是两种结果,然后我们把钱的数额累加,最后得到我们想要的数额。如果整幅图都没有想要的金额,那自然是无解需要输出No Solution。
好的,那么思路完了代码如下:

#include<iostream>
#include<queue>
#include<vector>
using namespace std;
int data[10001];//硬币的数组,用来储存每个硬币的金额
int farray[10001][101];//创建二维数组,用来储存f(i,k)的值,能够节省递归的时间
int n;//硬币的总个数
int needmoney;//需要凑的金额数
bool choose[10001];//记录每个硬币是否被选择,初始化为false表示没被选择
int f(int i,int k)//i表示第i个硬币,k表示需要凑的金额数
 {
 if(farray[i][k]>=0)//如果f(i,k)已经被计算过,直接返回值。
  return farray[i][k];
 if(i>n)//如果i>硬币数量了,返回0表示失败。
  return 0;
 if(k==0)//如果需要凑的钱为0了,返回1表示成功。
  return 1;
 if(data[i]-k==0) //如果当前硬币金额等于需要凑的钱,返回1成功,并把当前硬币choose改为true
 {
  farray[i][k]=1;
  choose[i]=true;
  return 1;
 } 
 else 
 {
  if(k<0) //如果需要的钱为负,返回失败
  {
   return 0;
  } 
  else //因为题目要求输出最小的一组解,所以优先判断“选择当前硬币”是否可行。
  {
   if(f(i+1,k-data[i])==1) //判断“选择当前硬币”是否可行。
   {
    farray[i][k]=1;
    choose[i]=true;
    return 1;
   } 
   else if(f(i+1,k)==1)//判断“不选择当前硬币”是否可行
    {
    farray[i][k]=1;
    return 1;
   } 
   else //如果上边两点都不可行表示无解
   {
    farray[i][k]=0;
    return 0;
   }
  }
 }
}
int main() {
 priority_queue<int,vector<int>,greater<int> > q;//这个代码用来将输入的硬币数额从小到大排列,不懂的可以CSDN查一下“priority_queue”的用法,主要是为了结果的输出。不排列的话,第一次样例结果可能会输出 5 3 1,为了保证结果按从小到大输出。
 cin>>n>>needmoney;
 for(int i=1; i<=n; i++) //初始化choose[i]和farry[i]
 {
  choose[i]=false;
  for(int j=0; j<=needmoney; j++)
   farray[i][j]=-1;
 }
 for(int i=0; i<n; i++) //将每一个硬币金额入队,入队完成后队列中为从小到大排列
 {
  int a;
  cin>>a;
  q.push(a);
 }
 for(int j=1; j<=n; j++) //将队列中从小到大排列的硬币金额赋值给硬币数组
 {
  data[j]=q.top();
  q.pop();
 }
 f(1,needmoney);//动态规划运算
 int count=0;//这个值是为了判断是否输No Solution,以及答案输出时的空格问题;
 for(int i=1; i<=n; i++) 
 {
  if(choose[i]) 
  {
   if(count++>0)//保证在有答案输出的情况下,第一个数前边没空格
    cout<<" ";
   cout<<data[i];
  }
 }
 if(count==0)
  cout<<"No Solution"<<endl;
 return 0;
}

代码完成了,感觉有的地方可能还有点不足,但整体没有问题,哈哈哈哈。
**PS:**1.有一个测试点,是测试所有的硬币金额加和正好为总需要金额。
2.如果储存f(i,k)的结果的数组farray是非常有必要的,当数据很大时,可以让时间复杂度变为O(n*needmoney);
最后给自己一个加油!!!!!!生命不息,代码不止!!!
2019年12月14日1:11
good night!

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值