这题由于大神题解写的比较详细,所以copy一下,原文链接:http://blog.csdn.net/shuangde800/article/details/7755452
题目大意:
某国家发行k种不同面值的邮票,并且规定每张信封上最多只能贴h张邮票。 公式n(h,k)表示用从k中面值的邮票中选择h张邮票,可以组成面额为连续的1,2,3,……n, n是能达到的最大面值之和。例如当h=3,k=2时, 假设两种面值取值为1,4, 那么它们能组成连续的1……6, 虽然也可以组成8,9,12,但是它们是不连续的了。
分析与总结:
连续邮资问题,这个算是很经典的一个问题了。王晓东的《计算机算法设计与分析第三版》 p173有介绍, 但是讲的不够详细, 这里转载了一篇博客对王晓东讲的有更深入详细分析:
连续邮资问题:http://blog.csdn.net/shuangde800/article/details/7755254
以下是按照我自己的话来总结:
首先开一个数组stampVal【0...i】来保存各个面值,再开一个maxVal[0...i]来保存当前所有面值能组成的最大连续面值。
那么,我们可以确定stampVal[0] 一定是等于1的。因为如果没有1的话,很多数字都不能凑成。
然后相应的,maxVal[0] = 1*h. h为允许贴邮票的数量
接下去就是确定第二个,第三个......第k个邮票的面值了,这个该怎么确定呢?
对于stampVal[i+1], 它的取值范围是stampVal[i]+1 ~maxVal[i]+1.
stampVal[i]+1好理解, 这一次取的面值肯定要比上一次的面值大, 而这次取的面值的上限是上次能达到的最大连续面值+1, 是因为如果比这个更大的话, 那么
就会出现断层, 即无法组成上次最大面值+1这个数了。 举个例子, 假设可以贴3张邮票,有3种面值,前面2种面值已经确定为1,2, 能达到的最大连续面值为6, 那么接下去第3种面值的取值范围为3~7。如果取得比7更大的话会怎样呢? 动手算下就知道了,假设取8的话, 那么面值为1,2,8,将无法组合出7 !!
知道了这个以后,就可以知道回溯的大概思路了, 但是还不够, 怎样取得给定的几个面值能够达到的最大连续面值呢?
最直观容易想到的就是直接递归回溯枚举所有情况, 便可知道最大连续值了。
下面是我写的递归函数:
// cur当前用了几张票, n面额数, sum面值之和
void dfs(int cur, int n, int sum){
if(cur >= h){
occur[sum] = true;
return;
}
occur[sum] = true;
for(int i=0; i<=n; ++i){
dfs(cur+1, n, sum+stampVal[i]);
}
}
occur是一个全局数组, 调用递归时先初始化为0。 然后用它来记录出现过的面值之和。
最后只需要从occur数组的下标1开始枚举,直到不是true值时就是能达到的最大连续面值。
由于这道题的数据量很小,所以这样做完全不会超时,速度也不错:
计算X[1:i]的最大连续邮资区间在本算法中被频繁使用到,如果数据量大的话,很可能就会超时了。
《计算机算法设计与分析第三版》给了一个更高效的方法:
考虑到直接递归的求解复杂度太高,我们不妨尝试计算用不超过m张面值为x[1:i]的邮票贴出邮资k所需的最少邮票数y[k]。通过y[k]可以很快推出r的值。事实上,y[k]可以通过递推在O(n)时间内解决:
for (int j=0; j<= x[i-2]*(m-1);j++)
if (y[j]<m)
for (int k=1;k<=m-y[j];k++)
if (y[j]+k<y[j+x[i-1]*k]) y[j+x[i-1]*k]=y[j]+k;
while (y[r]<maxint) r++;
转载的那篇博文对这个有更深入的分析。
下面是俩种做法的代码,第一种为注释部分:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <set>
#include<cmath>
#include<climits>
#include<cfloat>
using namespace std;
const int P = 1e9 + 7;
const int N=2000;
int h,k;
int ans[N],maxstampval,stampval[N],maxval[N],y[N];
bool occur[N];
/*void dfs(int cur,int n,int sum)
{
if(cur>=h)
{
occur[sum]=1;
return;
}
occur[sum]=1;
for(int i=0;i<=n;i++)
dfs(cur+1,n,stampval[i]+sum);
}*/
void search(int cur)
{
if(cur>=k)
{
if(maxval[cur-1]>maxstampval)
{
maxstampval=maxval[cur-1];
memcpy(ans,stampval,sizeof(stampval));
}
return;
}
int tem[N];
memcpy(tem,y,sizeof(y));
for(int i=stampval[cur-1]+1;i<=maxval[cur-1]+1;i++)
{
stampval[cur]=i;
for(int j=0;j<stampval[cur-1]*h;j++)
if(y[j]<h)
{
for(int num=1;num<=h-y[j];num++)
if(y[j]+num<y[j+i*num]&&(j+i*num<N))
y[j+i*num]=y[j]+num;
}
int r=maxval[cur-1];
while(y[r+1]<INT_MAX) r++;
maxval[cur]=r;
/* memset(occur,0,sizeof(occur));
dfs(0,cur,0);
int num=0,j=1;
while(occur[j++]) num++;
maxval[cur]=num;*/
search(cur+1);
memcpy(y,tem,sizeof(tem));
}
}
int main()
{
while(cin>>h)
{
cin>>k;
if(!h&&!k) break;
stampval[0]=1;
maxval[0]=h;
maxstampval=INT_MIN;
int i;
for(i=0;i<=h;i++)
y[i]=i;
while(i<N) y[i++]=INT_MAX;
search(1);
for(int i=0;i<k;i++)
printf("%3d",ans[i]);
printf(" ->%3d\n",maxstampval);
}
return 0;
}