[多重背包] POJ 1276 Cash Machine

一句话题意:

基本是多重背包裸题.


分析:

训练赛时的一道题,然而当时忘了多重背包是什么样的了…急中生智,发明了一种算法,感觉效率还是蛮高的.(之后在VJ上发现了一个和我一模一样思路的,也谈不上发明吧)
这是在完全背包上加了一个cnt数组,让完全背包无限放变成了有限个,cnt数组就是记录第i个物品用了几个.
下面先放上一个完全背包的代码:

for (int i = 1; i <= n; i++)
    for (int j = data[i].value; j <= cash; j++)
        if(dp[j - data[i].value])
            dp[j] = true;

如上面代码所示,dp[j]代表使用前i种面额(无限使用)能不能到达j.
但此题每种货币有数量限制,那么就增加一个cnt数组限制第i种面额的使用次数.cnt[j]就表示在全部前i-1种面额组成的所有面额的基础上,再用几张第i中面额,能组成j.
比如在前i种循环中dp[j - v]标记为true,第i中面额为v,数量为k,只要满足:
dp[j - v]为true,(j - v的数量,再加上第i种面额v,就能到达j)
且cnt[j - v] < k(这种货币的使用次数在组成j - v后使用不超过k - 1次)
那么使用第[i]种面额就能组成j.
但是能组成j,此时不要先标记在dp数组中,cnt数组中有标记就可以了,其实不用刻意的去标记,使用了一次第i中货币组成了[j],cnt数组的j位置出就是一个大于1的数.
现在理一理思路:
前i - 1种货币组成的面额标记在dp数组中,
在前i - 1种的基础上新组成的面额标记的cnt数组中(可能会有重复)
每一种货币跑完以后,就把cnt数组中的标记转移到dp数组中就可以了
我们用一次新的面额,都清零一次cnt数组,cnt数组里存的是当前物品的使用次数.

for (int i = 0; i <= n - 1; i++)
        {
            memset(cnt, 0, sizeof(cnt));//cnt数组标记的是第i种货币的使用次数,每次循环都要清零
            for (int j = data[i].value; j <= cash; j++)
            {
                if(dp[j - data[i].value] && data[i].num)
                {
                    cnt[j] = 1;//在dp中发现j可以达到,那么此时肯定使用的是第一张
                }
                else if(cnt[j - data[i].value] && cnt[j - data[i].value] < data[i].num)
                {
                    cnt[j] = cnt[j - data[i].value] + 1;//在cnt数组中发现j可以达到,那么肯定使用不止一张
                }
            }
            for (int j = 0; j <= cash; j++)//把cnt数组中的标记转移到dp数组中
                if(cnt[j])
                    dp[j] = true;
        }

这样跑完第i遍后,dp数组内存的就是所有能组成的面额的情况了.


代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <string>
#include <cstdio>

using namespace std;

bool dp[100010];
int cnt[100010];
struct bill
{
    int num, value;
}data[100];

int main()
{
    int cash, n;
    while(cin >> cash)
    {
        memset(dp, false, sizeof(dp));
        dp[0] = true;
        cin >> n;
        for (int i = 0; i <= n - 1; i++)
            scanf("%d %d", &data[i].num, &data[i].value);
        for (int i = 0; i <= n - 1; i++)
        {
            memset(cnt, 0, sizeof(cnt));//cnt数组标记的是第i种货币的使用次数,每次循环都要清零
            for (int j = data[i].value; j <= cash; j++)
            {
                if(dp[j - data[i].value] && data[i].num)
                {
                    cnt[j] = 1;//在dp中发现j可以达到,那么此时肯定使用的是第一张
                }
                else if(cnt[j - data[i].value] && cnt[j - data[i].value] < data[i].num)
                {
                    cnt[j] = cnt[j - data[i].value] + 1;//在cnt数组中发现j可以达到,那么肯定使用不止一张
                }
            }
            for (int j = 0; j <= cash; j++)//把cnt数组中的标记转移到dp数组中
                if(cnt[j])
                    dp[j] = true;
        }
        for (int i = cash; i >= 0; i--)
        {
            if(dp[i])
            {
                cout << i << endl;
                break;
            }
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值