韩梅梅喜欢满宇宙到处逛街。现在她逛到了一家火星店里,发现这家店有个特别的规矩:你可以用任何星球的硬币付钱,但是绝不找零,当然也不能欠债。韩梅梅手边有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;//这个代码用来将输入的硬币数额从小到大排列,不懂的可以优快云查一下“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!