邮票(DP)

题目描述

题目描述
已知一个 N 枚邮票的面值集合(如,{1 分,3 分})和一个上限 K —— 表示信封上能够贴 K 张邮票。计算从 1 到 M 的最大连续可贴出的邮资。
例如,假设有 1 分和 3 分的邮票;你最多可以贴 5 张邮票。很容易贴出 1 到 5 分的邮资(用 1 分邮票贴就行了),接下来的邮资也不难:
6 = 3 + 3
7 = 3 + 3 + 1
8 = 3 + 3 + 1 + 1
9 = 3 + 3 + 3
10 = 3 + 3 + 3 + 1
11 = 3 + 3 + 3 + 1 + 1
12 = 3 + 3 + 3 + 3
13 = 3 + 3 + 3 + 3 + 1。
然而,使用 5 枚 1 分或者 3 分的邮票根本不可能贴出 14 分的邮资。因此,对于这两种邮票的集合和上限 K=5,答案是 M=13。

输入
第 1 行:两个整数,K 和 N。K(1 <= K <= 200)是可用的邮票总数。N(1 <= N <= 50)是邮票面值的数量。
第 2 行:文件末: N 个整数,每行 15 个,列出所有的 N 个邮票的面值,面值不超过 10000。

输出
第 1 行:一个整数,从 1 分开始连续的可用集合中不多于 K 张邮票贴出的邮资数。
样例输入
5 2
1 3
样例输出
13

分析

这道题是一道DP,很容易看出来,它满足DP的两大判定(It is very important!!!):
①最优化原理,因为它的每个邮资能否由k张及以下(设为x张)邮票组成肯定是由之前所有由x-1张邮票组成的邮资中选取其中合理的转移过来的。
②无后效性原则,每个邮资改变后只会改变比它大的邮资。

作者赶脚这是一道背包,然而这就是!!!让我们来看两组简单的数据分析一下:

数据1:

样例输入
2 2
1 5
样例输出
2
这里首先我们先考虑邮资为1,似乎只有取1,邮票用了1张;
邮资为2,由邮资1的情况加1就是2,邮票用了2张;
邮资为3,由邮资为2的情况加1就是3,邮票用了3张,大于k(2)张,不行,但是用邮票面值为5的又大于3。
所以直接输出2。

数据2:

样例输入
2 2
1 2
样例输出
4
邮资为1时,取1,邮票用了1张;
邮资为2时,由邮资1情况加1就是2,邮票用了2张;可还可以由没用的情况直接用邮票面值为2,邮票用了1张,这里各种邮票有无数张,只是只能取k张,显然后者优于前者,所以邮资2邮票用了1张;
邮资为3时,由邮资1加邮票面值为2一张,邮票用了2张
有人会问:为什么不由邮资为1加两张面值为1的邮票?(如果可以用3张的话),我的回答是:因为这是DP,由刚刚的判定最优化原理它的每个邮资能否由k张及以下(设为x张)邮票组成肯定是由之前所有由x-1张邮票组成的邮资中选取其中合理的转移过来的。
好的继续,由邮资2加邮票面值为1一张,邮票也用了2张,改不改变随便(建议不改,因为改了会加一点点时间)。所以邮资为3时,邮票用了2张。
邮资为4时,如果由邮资为3加一张面值为1的邮票,邮票用了3张比k(2)大,所以不行,由邮资2加上一张面值为2的邮票,邮票用了2张可以,所以邮票用了2张,由邮资1无法转移到邮资4,因为没有一张邮票面值为3。
由于这是求 1 到 M 的最大连续可贴出的邮资,由于DP无后效性原则,每个邮资改变后只会改变比它大的邮资,它自身不会再改变,所以只要有一个邮资处理完后所用邮票数大于k,则直接输出当前邮资减1,即1 到 M 的最大连续可贴出的邮资。
DP我们正着推完了,但是我们要当前位置回头用之前的邮资推,也是正着写,就如放风筝一样,你要往前跑但又要回头注意风筝~
那么递推式十分明了了:
令f[i]为凑成邮资为i的最小邮票数,则有两种状态转移方程式:

人人为我填表型

f[i]={min{f[istamp[j]]+1}INFi stamp[j]i 1f[i-stamp[j]]INFstamp[j]>i

我为人人刷表型

f[i+stamp[j]]={min{f[i]+1}

还有,像之前分析的一样,只要有一个f[i]处理后大于k,那么就输出i-1(貌似作者只是将当前的话翻译成了处理方式)
还有(作者你不能一次性把话说完吗…),由于求最小值,所有f[i]初值均为INF。
好了,思路分析讲完了,接下来开始秀代码(对了,stamp意思是邮票)。

代码

代码一

#include<cstdio> 
#include<cstring> 
#include<climits> 
#include<iostream> 
#include<algorithm>
#define INF 1000000
using namespace std;
int read()//“高级的”读入优化
{
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||'9'<c){if(c=='-')f=-1;c=getchar();}
    while('0'<=c&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
int stamp[55],f[2000005];//stamp[i]表示邮票i的面值,面值为i所用最少邮票数f[i]
//f[i]=min{f[i-stamp[j]]+1||INF};
//( 1<=f[i]<=k    stamp[j]<=i    1<=j<=n )
int main()//1到m的最大连续可贴出的邮资
{//k是可用的邮票总数n是邮票面值的数量
    int k=read(),n=read(),i=0;
    for(int i=1;i<=n;i++)
        stamp[i]=read();
    sort(stamp+1,stamp+n+1);
    while(f[i]<=k)
    {//只要有i邮资处理完后f[i]所用邮票数大于k可用的邮票,则直接输出当前i-1
        i++;
        f[i]=INF;//赋初值
        for(int j=1;j<=n&&i>=stamp[j];j++)
            if(f[i-stamp[j]]+1<f[i])
                f[i]=f[i-stamp[j]]+1;
    }
    printf("%d\n",i-1);
    return 0;
}

代码二

#include<cstdio> 
#include<cstring> 
#include<climits> 
#include<iostream> 
#include<algorithm>
#define INF 10000000
using namespace std;
int read()//“高级的”读入优化
{
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||'9'<c){if(c=='-')f=-1;c=getchar();}
    while('0'<=c&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
int stamp[55],f[2020005];//stamp[i]表示邮票i的面值,面值为i所用最少邮票数f[i]
//f[i]=min{f[i-stamp[j]]+1||INF};
//( 1<=f[i]<=k    stamp[j]<=i    1<=j<=n )
int main()//1到m的最大连续可贴出的邮资
{//k是可用的邮票总数n是邮票面值的数量
    int k=read(),n=read(),i=0;
    for(int i=1;i<=n;i++)
        stamp[i]=read();
    sort(stamp+1,stamp+n+1);
    for(int i=1;i<=2020000;i++)
        f[i]=INF;
    f[0]=0;
    while(f[i]<=k)
    {//只要有i邮资处理完后f[i]所用邮票数大于k可用的邮票,则直接输出当前i-1
        i++;
        for(int j=1;j<=n;j++)
            if(f[i]+1<f[i+stamp[j]])
                f[i+stamp[j]]=f[i]+1;
    }
    printf("%d\n",i-1);
    return 0;
}

Thanks for Reading!!!

猜你喜欢

转载自blog.csdn.net/qq_37555704/article/details/78784224