UVA165连续邮资问题

52 篇文章 0 订阅
27 篇文章 0 订阅
博客介绍了UVA165题目的连续邮资问题,讨论了如何确定邮票面值以形成连续的邮资面额。文章通过分析问题本质,提出递归回溯和优化算法,解释了如何在限制邮票数量和面值的情况下找到最大连续面值。
摘要由CSDN通过智能技术生成

这题由于大神题解写的比较详细,所以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;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值