清华机试-最小邮票数
题目描述
有若干张邮票,要求从中选取最少的邮票张数凑成一个给定的总值。 如,有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; }