【usaco/codevs2033/codevs1047/NOIP1999TG】 邮票问题浅谈

来源:互联网 发布:电驴下载软件 编辑:程序博客网 时间:2024/04/30 09:55

1、邮票  usaco/codevs2033黄金Gold

题目描述 Description

已知一个 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


小提示:因为14贴不出来,所以最高上限是13而不是15

输入描述 InputDescription

1行:两个整数,K NK1 <= K <= 200)是可用的邮票总数。N1 <= N <= 50)是邮票面值的数量。

2 ..文件末: N 个整数,每行 15个,列出所有的 N个邮票的面值,每张邮票的面值不超过 10000

输出描述 OutputDescription

1行:一个整数,从 1分开始连续的可用集合中不多于 K张邮票贴出的邮资数。

样例输入 Sample Input

5 2

1 3

样例输出 Sample Output

13

数据范围及提示 Data Size& Hint

 

【数据分析】【空间复杂度可以承受】

最多贴k<=200张,每张面值不超过10^4,所以2*10^6可以用一个一维数组存储。

 

【算法分析】【深搜TLE用dp】

简单的暴搜时间复杂度会达到50^20,是我们难以承受的;

我们可以用递推来做:

阶段:以能够成的面值为每个阶段。如样例中一共有13个阶段;

状态:f[i]表示构成面值i所需的最少邮票个数;

决策:如果当前能组成的面值i减去某一张邮票的面值再+1<f[i]的话,就说明构成面值i的邮票个数还可以更少,就是说用当前的那一张邮票来组成面值i的话,是当前的最优解,,用状态转移方程来表示:f[i]=min(f[i],f[i-a[j]]+1);

这就是dp的思路,时间复杂度O(nk);

 

【代码】

<span style="font-size:18px;">#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;int n,k,i,j,a[100000],f[100000];int main(){        scanf("%d%d",&n,&k);        for(i=1;i<=n;++i)          scanf("%d",&a[i]);        sort(a+1,a+n+1);        f[0]=0;        i=0;        while(f[i]<=k)        {               i++;               f[i]=2147483647;               for(j=1;j<=n;++j)                 if (a[j]<=i&&f[i-a[j]]+1<f[i])                   f[i]=f[i-a[j]]+1;        }        printf("%d",i-1);        return0;}</span>


2、邮票面值设计 NOIP1999TG/codevs1047钻石Diamond

题目描述 Description

 

给定一个信封,最多只允许粘贴N张邮票,计算在给定KN+K≤40)种邮票的情况下(假定所有的邮票数量都足够),如何设计邮票的面值,能得到最大值MAX,使在1MAX之间的每一个邮资值都能得到。

   

    例如,N=3K=2,如果面值分别为1分、4分,则在1分~6分之间的每一个邮资值都能得到(当然还有8分、9分和12分);如果面值分别为1分、3分,则在1分~7分之间的每一个邮资值都能得到。可以验证当N=3K=2时,7分就是可以得到的连续的邮资最大值,所以MAX=7,面值分别为1分、3分。

输入描述 InputDescription

NK

输出描述 OutputDescription

每种邮票的面值,连续最大能到的面值数。数据保证答案唯一。

样例输入 Sample Input

3 2

样例输出 Sample Output

1 3

MAX=7

【数据分析】【空间复杂度可以接受】

 N+K<=40;4*10^6(学姐说最高可达3*10^7)

【算法分析】【深搜+dp】

 N+K<=40; 

 与上一题的区别在于还需枚举邮票的面值,枚举的过程可以用深搜来实现;

首先明确:

第一个面值一定是1,假如说最多贴n张,要保证连续,那么第二张面值最大是1*n+1,最小是1+1;也就是说,如果已经选定了dep-1个面值,现在要选第dep个面值,那么第dep个面值的范围只能是a[dep-1]+1到a[dep-1]*n+1,这也就规定了枚举的范围,那么用深搜就好做很多了;

我们可以用深搜生成很多面值的组合,然后再用类似前一题的dp来求出最大的连续面值,即是这道题的答案。

 

【代码】

<span style="font-size:18px;">#include<iostream>#include<cstdio>#include<cstring>using namespace std;int f[4000005],a[50],ans[50];int n,k,maxn,i; void work()//和上面的dp过程基本相同,ans和maxn是最终的答案{        inti,j;        f[0]=0;        i=0;        while(f[i]<=n)        {               i++;               f[i]=2147483647;               for(j=1;j<=n;++j)                 if (i>=a[j]&&f[i-a[j]]+1<f[i])                   f[i]=f[i-a[j]]+1;        }        if(i-1>maxn)        {               maxn=i-1;               for(j=1;j<=k;++j)                 ans[j]=a[j];        }        return;} void dfs(int dep){        intr;        if(dep==k+1)        {               work();//如果已经生成好了一种可能的组合,就进行一次dp               return;        }        for(r=a[dep-1]+1;r<=a[dep-1]*n+1;++r)//这就是前面说的枚举的范围        {               a[dep]=r;//a是记录数组               dfs(dep+1);        }        return;} int main(){        freopen("a5.in","r",stdin);        freopen("a5.out","r",stdout);        scanf("%d%d",&n,&k);        maxn=0;        dfs(1);//首先深搜生成所有可能的面值组合        for(i=1;i<=k;++i)          printf("%d ",ans[i]);        printf("\n");        printf("MAX=%d",maxn);        return0;}</span>

邮票问题是经典问题,在学递推和搜索的时候都出现了。通过这道题可以更深刻的理解搜索与dp各自的原理以及他们的结合。

0 0