uva 165 - Stamps

4 篇文章 0 订阅

看了各种大神的代码后,终于知道怎么写了。

参考

http://blog.csdn.net/shuangde800/article/details/7755452

http://blog.csdn.net/jcwkyl/article/details/4137398

与第一个链接对应的两种解法

暴力

首先开一个数组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 !!


知道了这个以后,就可以知道回溯的大概思路了, 但是还不够, 怎样取得给定的几个面值能够达到的最大连续面值呢?

最直观容易想到的就是直接递归回溯枚举所有情况, 便可知道最大连续值了。

由于这道题的数据量很小,所以这样做完全不会超时,速度也不错:

 

 

#include <iostream>
#include <cstring>
#include<cstdio>
#include<cstdlib>
#include <string>
#include<algorithm>
#include<cmath>
#define set0(a) memset(a, 0, sizeof(a))
//#define MARK -2147483647
using namespace std;
int n,kind;
int ans[20],ansmax;
int maxstamp[20],stampkind[20];int you[1000];
void qiuzhi(int zhang,int cur,int sum,int kind)
{
    int i;
    if(zhang==n)return;
    if(cur>kind)return;
    for(i=0;i<=n;++i)
    { if(zhang+i<=n)
        {   you[sum+i*stampkind[cur]]=1;
            qiuzhi(zhang+i,cur+1,sum+i*stampkind[cur],kind);
        }
    }
}
void sear(int cur)
{
    int i;
    if(cur>=kind)
    {   //cout<<maxstamp[cur-1]<<endl;
        if(maxstamp[cur-1]>ansmax)
        {
            ansmax=maxstamp[cur-1];
            for(i=0;i<cur;++i)
            {
               ans[i]= stampkind[i];
            }
        }
        return;
    }
    for(i=stampkind[cur-1]+1;i<=maxstamp[cur-1]+1;++i)
    {
        stampkind[cur]=i;
        you[0]=1;
        set0(you);
        qiuzhi(0,0,0,cur);
        int maxs;
        int ii;
        for(ii=1;;++ii)if(you[ii]!=0)maxs=ii;else break;
        maxstamp[cur]=maxs;
        sear(cur+1);
    }
}
int main()
{
// freopen("in.txt","r",stdin);
  while(scanf("%d%d",&n,&kind))
  {
      if(!n&&!kind)break;
      stampkind[0]=1;
      maxstamp[0]=n;
      ans[0]=1;
      ansmax=n;
      sear(1);
      int i;
        for(i=0;i<kind;++i)
        {
            printf("%3d",ans[i]);
        }
        printf(" ->");
        printf("%3d\n",ansmax);
  }
    return 0 ;
}

计算X[1:i]的最大连续邮资区间在本算法中被频繁使用到,如果数据量大的话,很可能就会超时了。

《计算机算法设计与分析第三版》给了一个更高效的方法:

第二个问题自己有两种思路:,计算出所有使用不超过mx[1…i+1]中的面值能够贴出的邮资,然后从r+1开始逐个检查是否被计算出来。二,从r+1开始,逐个询问它是不是可以用不超过mx[1…i+1]中的面值贴出来。
两种思路直接计算其计算量都是巨大的,需要借助动态规划的方法。模仿0-1背包问题,假设S(i)表示x[1…i]中不超过m张邮票的贴法的集合,这个集合中的元素数目是巨大的,例如,只使用1张邮票的贴法有C(i+1-1,1)C(i,1)=i种,使用2张邮票的贴法有C(i+2-1,2)=C(i+1,2)=i*(i+1)/2种,……,使用m张邮票的贴法有C(i+m-1, m)种,其中C(n,r)表示n元素中取r元素的组合数。于是,S(i)中的元素的数目总共有C(i+1-1, 1) + C(i+2-1,2)+ … + C(i+m-1,m)S(i)中的每个元素就是一种合法的贴法,对应一个邮资。当前最大连续邮资区间为1r,那么S(i)中每个元素的邮资是不是也在1r之间呢?不一定,比如{1,2,4},当m=2时,它能贴出来8,但不能贴出来7,这一点自己在写代码时犯了错误。总之,在搜索时,一定要保持状态的一致性,即当深度搜索到第i层时,一定要确保用来保存结点状态的变量中保存的一定是第i层的这个结点的状态。言归正传,定义S(i)中元素的值就是它所表示的贴法贴出来的邮资,于是,可以把S(i)中的元素按照它们的值的相等关系分成k类。第j类表示贴出邮资为j的所有的贴法集合,用T(j)表示,T(j)有可能是空集,例如对于{1,2,4},T(7)为空集,T(8)={{4,4}}。此时有:S(i) = T(1) U T(2) U T(3) U … U T(k)U表示两个集合的并。
现在考虑x[i+1]加入后对当前状态S(i)的影响。假设sS(i)中的一个元素,即s表示一种合法的贴法x[i+1]s能贴出的邮资的影响就是x[i+1]的多次重复增加了s能贴出的邮资。这样说是因为有两种情况不需要考虑:, 从s中去掉几张邮票,把x[i+1]加进去,这没有意义,因为从s中去掉几张邮票后s就变成了S(i)中的另一个元素t,我们迟早会对t考虑x[i+1]的影响的。二,将x[i+1]加入s,同时再把x[1]也加入s(如果s中还能再贴两张邮票的话),这也没有意义,原因同一。所以,x[i+1]s的影响就是,如果s中贴的邮票不满m张,那就一直贴x[i+1],直到s中有m张邮票,这个过程会产生出很多不同的邮资,它们都应该被加入S(i+1)中。因为s属于S(i),它也必定在某个T(k)中,而T(k)中能产生出最多不同邮资的是T(k)中用的邮票最少的那个元素。至此,原书中的解法就完全出来了:用数组x记录当前已经确定的邮票面值,用r表示当前最大的连续邮资区间,用数组y表示用当前的面值贴出某个邮资所需要的最少的邮票数。状态结点的转换过程已经在上面说的非常清楚了。现在只差写代码了。代码如下:

dp

#include <iostream>
#include <cstring>
#include<cstdio>
#include<cstdlib>
#include <string>
#include<algorithm>
#include<cmath>
#define set0(a) memset(a, 0, sizeof(a))
#define MARK 2147483647
using namespace std;
int n,kind;
int ans[20],ansmax;
int maxstamp[20],stampkind[20];int you[1001];
//void qiuzhi(int zhang,int cur,int sum,int kind)
//{
//    int i;
//    if(zhang==n)return;
//    if(cur>kind)return;
//    for(i=0;i<=n;++i)
//    { if(zhang+i<=n)
//        {   you[sum+i*stampkind[cur]]=1;
//            qiuzhi(zhang+i,cur+1,sum+i*stampkind[cur],kind);
//        }
//    }
//}
void sear(int cur)
{
    int i;
    if(cur>=kind)
    {   //cout<<maxstamp[cur-1]<<endl;
        if(maxstamp[cur-1]>ansmax)
        {
            ansmax=maxstamp[cur-1];
            for(i=0;i<cur;++i)
            {
               ans[i]= stampkind[i];
            }
        }
        return;
    }
    int cun[1001];
    memcpy(cun,you,sizeof(you));
    for(i=stampkind[cur-1]+1;i<=maxstamp[cur-1]+1;++i)
    {
        stampkind[cur]=i;
       // set0(you);
      //  qiuzhi(0,0,0,cur);
        int maxs;
        int ii;int add;
        for(ii=0;ii<stampkind[cur-1]*n;++ii)
        if(you[ii]<n)
            for(add=1;add<=n-you[ii];++add)
            if(you[ii+add*i]>add+you[ii])
        {you[ii+add*i]=add+you[ii];}
        for(ii=maxstamp[cur-1];;++ii)if(you[ii]!=MARK)maxs=ii;else break;
        maxstamp[cur]=maxs;
        sear(cur+1);
        memcpy(you,cun,sizeof(cun));
    }
}
int main()
{
// freopen("in.txt","r",stdin);
  while(scanf("%d%d",&n,&kind))
  {
      if(!n&&!kind)break;
      stampkind[0]=1;
      maxstamp[0]=n;
      ans[0]=1;
      ansmax=n;
      int i;
      for(i=0;i<=1000;++i)
        you[i]=MARK;
      for(i=0;i<=n;++i)
        you[i]=i;
      sear(1);
        for(i=0;i<kind;++i)
        {
            printf("%3d",ans[i]);
        }
        printf(" ->");
        printf("%3d\n",ansmax);
  }
    return 0 ;
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值