1068. Find More Coins (30)

题目如下:

Eva loves to collect coins from all over the universe, including some other planets like Mars. One day she visited a universal shopping mall which could accept all kinds of coins as payments. However, there was a special requirement of the payment: for each
bill, she must pay the exact amount. Since she has as many as 104 coins with her, she definitely needs your help. You are supposed to tell her, for any given amount of money, whether or not she can find some coins to pay for
it.

Input Specification:

Each input file contains one test case. For each case, the first line contains 2 positive numbers: N (<=104, the total number of coins) and M(<=102, the amount of money Eva has to pay). The second
line contains N face values of the coins, which are all positive numbers. All the numbers in a line are separated by a space.

Output Specification:

For each test case, print in one line the face values V1 <= V2 <= ... <= Vk such that V1 + V2 +
... + Vk = M. All the numbers must be separated by a space, and there must be no extra space at the end of the line. If such a solution is not unique, output the smallest sequence. If there is no solution, output "No Solution"
instead.

Note: sequence {A[1], A[2], ...} is said to be "smaller" than sequence {B[1], B[2], ...} if there exists k >= 1 such that A[i]=B[i] for all i < k, and A[k] < B[k].

Sample Input 1:

8 9
5 9 8 7 2 3 4 1

Sample Output 1:

1 3 5

Sample Input 2:

4 8
7 2 4 3

Sample Output 2:

No Solution

题目要求从一系列硬币中找出最小的、满足给定面值和的硬币序列,这属于典型的背包问题,问题的难点在于输出最小序列。

【如何找到这样的序列】

我们设f(i,j)表示从前i个硬币中选出的面值不大于j的最大的面值和,设硬币序列中第i个硬币的面值为c[i],则f(i,j)的计算可以分为两种情况递推考虑:

每个硬币都有放入和不放入两种情况。

①放入第i个硬币,即f(i-1,j - c[i]) + c[i]。

②不放入第i个硬币,即f(i-1,j)。

要保证面值最大,应该选择二者中的较大者,因此递推式如下:

1068. Find More Coins (30)

因此我们只要从i=1~N,分别解决j=1~M的问题,便可以得到不超过M的最大面值,通过判断f(N,M),如果它=M,说明能够找到这样的序列,否则说明找不到,因为它是不超过M的,从所有硬币中能组合出的最大的和。

【如何使序列最小】

接下来我们解决序列最小的问题,我们这样考虑,每当加入一个c[i],序列中就会多一个元素,如果我们让硬币按照降序排列,这样每一次加入c[i],都相当于加入了一个更小的序列头,例如现在的序列是{5,3,2},因为是降序,c[i]≤2,因此只要加入c[i],序列会编程{5,3,2,c[i]},这个序列必定变小,因此我们应该记录下所有加入c[i]时的下标,最后根据这些下标回溯,即可得到最小的序列。

以下代码来着tiantangrenjian,感谢他提供的解法;关于最小序列的找法,学习自gzxcyy,感谢他的详尽分析。

#include <iostream>
#include <fstream>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std; #define MAXTOTAL 10001
#define MAXAMOUNT 101 int f[MAXTOTAL][MAXAMOUNT]; //f[n][m]表示 前n个数中 得出的 最接近m 的值
bool has[MAXTOTAL][MAXAMOUNT]; //has[n][m]表示在前n个数中得出最接近m的值时 是否用到c[n]
int* c = NULL; int calcClosestSum(int n,int m)
{
memset(f,0,sizeof(int)*MAXTOTAL*MAXAMOUNT);
memset(has,false,sizeof(bool)*MAXTOTAL*MAXAMOUNT);
int i,j;
int sec; // 表示放入c[i]后的值
for(i=1;i<n+1;i++)
{
for(j=1;j<=m;j++)
{
if(j-c[i]<0) sec=0; // 如果不满足小于等于j,则说明放入后是非法值,可以设其为0,表示面值无效。
else sec = f[i-1][j-c[i]]+c[i]; // 正常情况下计算放入c[i]的值。
if(f[i-1][j] > sec)
{
f[i][j]=f[i-1][j];
}else
{
f[i][j]=sec;
has[i][j]=true; //用到c[i]了 设has[i][j]为true
}
}
}
return f[n][m];
} bool cmp(const int& A,const int& B)
{
return A>B;
} int main()
{
int n,m;
cin>>n>>m;
c = new int[n+1];
memset(c,0,sizeof(int)*(n+1)); int i;
for(i=0;i<n;i++)
{
cin>>c[i+1];
}
sort(&c[1],&c[n+1],cmp); //从大到小排序 int res = calcClosestSum(n,m);
if(res==m) //有解
{
vector<int> v;
while(m)
{
while(!has[n][m])
n--;
v.push_back(c[n]);
m = m - c[n];
n--;
}
for(i=0;i<v.size()-1;i++)
cout<<v[i]<<' ';
cout<<v[i]<<endl;
}else //无解
cout<<"No Solution"<<endl;
return 0;
}
上一篇:Java并发编程原理与实战十:单例问题与线程安全性深入解析


下一篇:20165232 2017-2018-2《Java程序设计》课程总结