清华机试-最小邮票数

题目描述

    有若干张邮票,要求从中选取最少的邮票张数凑成一个给定的总值。     如,有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;

}

  

原文地址:https://www.cnblogs.com/tracy520/p/8836340.html