POJ 1276--Cash Machine

  • 较为简单的背包DP。设dp[i][j] = 1,到第i种钞票能形成cash j,否则为0. 则很容易得出转移方程,若dp[i][j] = 1,则dp[i+1][j+k*d[i+1]] = 1,其中k在[0,n[i+1]]之间,n[i+1]为第i中钞票数量,d[i]为其面额。若直接暴力计算复杂度为N*M*n。很容易想到若直接枚举i中的j与k个d[i+1]的和,有很多重复计算,例如若第i中面额产生了2,4的cash,若i+1的面额为2,则i中的cash加上d[i+1]又到了4。
  • 参考楼天城八题中的硬币方案中的算法:
  1. 在更新由第i+1种货币产生的cash前对前i中产生的cash遍历并给增值数组 billRemain赋初值为billNum[i+1],表示从此cash可以向后增值billRemain次第i+1种面额。
  2.   再次遍历上次货币产生的cash,此时若此cash能增值,并且其增值到的cash 还没有在前i次中产生,就标记增值后的cash,并且将新cash能增值的数目变为此时cash增值数目减一。
  3. 为什么要限定增值后的cash没出现过,因为若可以增值到的cash已经可以增值,说明它已经由前i次货币产生,则它可以增值的数目显然比从现在的cash增值过去 要多,并且也避免了上面提到的重复标记。
  • 内存优化:首先标记数组是不需要的,可以直接利用增值数组来作为cash是否存在的标记。因为若一个cash存在,则经过算法它的增值数组值肯定大于等于0。如果cash不存在,则会一直标记成-1.可以从它的变化看出来,首先经过赋值会大于等于0,又因为新产生cash时规定现在的cash能增值的数目必须大于等于1,所以其增值到的cash可增值数目就大于等于0.然后其实两次遍历可以写在一起,因为若在第i次cash更新后某cash存在,则增值数组下一次就等于billNum[i+1],所以可以根据无后效性,因为cash更新时会遍历所有存在的cash,可以当其增值数组值使用后(不会再使用)直接赋billNum[i+1]给它。注意边界条件,当i遍历到最后一种货币时,赋值时会越界,所以赋1给越界值。

#include<cstdio>
#include<cstdlib>
#define _min(x,y) ((x)>(y)?(y):(x))
#define maxCash  100002
#define maxDenoNum 12

struct node
{
    int deno;
    int billNum;
}d[maxDenoNum];

int cmp(const void* x,const void* y)
{
    return ((node*)x)->deno*((node*)x)->billNum - ((node*)y)->deno*((node*)y)->billNum;
}

short billRemain[maxCash];
int main()
{
    int i,j;
    int avaCash,minCash;
    int cash,denoNum;
    while(scanf("%d%d",&cash,&denoNum) != EOF)
    {
        memset(billRemain,255,sizeof(short)*(cash+1));
        avaCash = 0;
        minCash = 0;
        for(i = 1;i <= denoNum;i++)
            scanf("%d%d",&d[i].billNum,&d[i].deno);
        qsort(d+1,denoNum,sizeof(node),cmp);
        d[denoNum+1].billNum = 1;
        i = 1;
        while(!d[i].billNum&&i++);          //排除增值为0的情况
        billRemain[0] = d[i].billNum;
        for(;i <= denoNum;i++)
        {
            avaCash += d[i].deno*d[i].billNum;
            minCash = _min(avaCash,cash);
            for(j = 0;j <= minCash;j++)
            if(billRemain[j] != -1)
            {
                if(billRemain[j]&&j+d[i].deno <= cash&&billRemain[j+d[i].deno] == -1)
                    billRemain[j+d[i].deno] = billRemain[j]-1;
                billRemain[j] = d[i+1].billNum;
            }
        }
        for(i = minCash;i >= 0;i--)
        if(billRemain[i] != -1)
        {
            printf("%d\n",i);
            break;
        }
    }
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值