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和 N。K(1 <= K <= 200)是可用的邮票总数。N(1 <= 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张邮票,计算在给定K(N+K≤40)种邮票的情况下(假定所有的邮票数量都足够),如何设计邮票的面值,能得到最大值MAX,使在1~MAX之间的每一个邮资值都能得到。
例如,N=3,K=2,如果面值分别为1分、4分,则在1分~6分之间的每一个邮资值都能得到(当然还有8分、9分和12分);如果面值分别为1分、3分,则在1分~7分之间的每一个邮资值都能得到。可以验证当N=3,K=2时,7分就是可以得到的连续的邮资最大值,所以MAX=7,面值分别为1分、3分。
输入描述 InputDescription
N和K
输出描述 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各自的原理以及他们的结合。