题目描述
有若干张邮票,要求从中选取最少的邮票张数凑成一个给定的总值。 如,有1分,3分,3分,3分,4分五张邮票,要求凑成10分,则使用3张邮票:3分、3分、4分即可。
输入描述:
有多组数据,对于每组数据,首先是要求凑成的邮票总值M,M<100。然后是一个数N,N〈 20,表示有N张邮票。接下来是N个正整数,分别表示这N张邮票的面值,且以升序排列。
输出描述:
对于每组数据,能够凑成总值M的最少邮票张数。若无解,输出0。
示例1
输入
10
5
1 3 3 3 4
输出
3
解题思路
一开始看到这个题目,想到的是bfs。bfs的思路是:枚举所有的可能性,最多有2的19次方种情况(524288种)。可以从0列到524287,一个数的二进制形式表示选或者不选这张邮票。由于对于每一个数字最多要列举19位,所以时间复杂度大约是O(19*2^19)。比较高但是应该也是可以通过的,因为其实有一些数字不用列举那么多位。
后来想到可以用动态规划来解决这个问题。如果将状态dp[i][j]定义为前i张邮票凑成总值j所需要的最少邮票数。则状态可以初始化为dp[i][0]=0。(其中,1<=i<N,0<=j<M)。
状态转移方程如下:
dp[i][j] = min(dp[i-1][j],dp[i-1][j-v[i]])。(其中,i>0,j-v[i]>=0)
同时注意判断dp[i][j]是否等于-1。-1表示dp[i][j]无解。
代码
#include <iostream>
#include <cmath>
using namespace std;
int dp[20][100] = {-1};
int num[20];
int main()
{
cout << pow(2,19) * 19 << endl;
int m,n;
while(cin >> m >> n)
{
int ans = 20;
for(int i = 0;i < n;i++)
{
dp[i][0] = 0;
for(int j = 1;j <= m;j++)
dp[i][j] = -1;
}
for(int i = 0;i < n;i++)
cin >> num[i];
for(int i = 0;i < n;i++)
{
for(int j = num[i];j <= m;j++)
{
if(i == 0)
{
if(j == num[i]) dp[i][j] = 1;
else dp[i][j] = -1;
}
else
{
int a = 20;
if(dp[i - 1][j - num[i]] != -1)
a = dp[i - 1][j - num[i]] + 1;
int b = 20;
if(dp[i - 1][j] != -1)
b = dp[i - 1][j];
int c = min(a,b);
if(c == 20) dp[i][j] = -1;
else dp[i][j] = c;
}
}
}
if(dp[n - 1][m] == -1) cout << 0 << endl;
else cout << dp[n - 1][m] << endl;
}
return 0;
}