题目描述
给定一个信封,最多只允许粘贴N张邮票,计算在给定K(N+K≤40)种邮票的情况下(假定所有的邮票数量都足够),如何设计邮票的面值,能得到最大值MAX,使在1~MAX之间的每一个邮资值都能得到。
输入
第1行:2个整数N K
输出
第一行从小到大写出所用邮票面值,
第二行写"MAX="MAX
样例输入
3 2
样例输出
1 3
MAX=7
分析
这是一道多年前的NOIP题,数据比较小,现在比较容易做,但是数据变大之后暴力就没办法做了,需要找到一个比较普遍的做法。观察题目,感觉没有什么特别好的方法,想到用DFS,搜索n张k种邮票所能够连续达到的最大值。根据题意,面值为1分的邮票一定会被选中,所以从第2张邮票的面值开始枚举。分析可知,第k张邮票的面值不会超过使用n张前k-1种邮票所能达到的最大值+1,确定了枚举的上界;而如果第k种邮票的面值<第k-1种,那么所能达到的当前最大值一定不会是最优的(第k-1种邮票就已经能够达到)。在寻找当前所能达到的最大值时使用揹包的思路,这样就能较高效地解决这道题。
代码如下:
#include<cstdio>
#define MAXN 40+5
#define INF (1<<29)
using namespace std;
int n,k;
int max(int a,int b)
{
returna>b?a:b;
}
int val[MAXN],maxv[MAXN],op[MAXN],f[100*MAXN];//val[i]是第i种邮票的面值,maxv[i]是在前i种邮票最多n张的情况下所能连续达到的最大值,op保存了最后的输出结果
int maxn;
void dp(int s)
{
intj,l=s-1;
while(f[l]<=n)
{
f[++l]=INF;
for(j=1;j<=s&&l>=val[j];j++)//由于之前已经做过之前的揹包,并且回溯后做过的揹包也不会对结果产生影响,可以优化节省时间
if(f[l]>f[l-val[j]]+1)
f[l]=f[l-val[j]]+1;
}
if(l-1>maxv[s])
maxv[s]=l-1;
}
void dfs(int s)
{
intv;
if(s==k+1)
{
if(maxv[s-1]>maxn)
{
maxn=maxv[s-1];
for(v=1;v<=k;v++)
op[v]=val[v];
}
return;
}
for(v=maxv[s-1]+1;v>val[s-1];v--)
{
val[s]=v;
dp(s);
dfs(s+1);
}
}
int main()
{
inti;
scanf("%d%d",&n,&k);
val[1]=1,maxv[1]=n;
for(i=1;i<=n;i++)
f[i]=i;
dfs(2);
printf("%d",op[1]);
for(i=2;i<=k;i++)
printf(" %d",op[i]);
printf("\nMAX=%d",maxn);
}