[多重背包] POJ 1276 Cash Machine

来源:互联网 发布:js 创建class 编辑:程序博客网 时间:2024/06/01 07:29

一句话题意:

基本是多重背包裸题.


分析:

训练赛时的一道题,然而当时忘了多重背包是什么样的了…急中生智,发明了一种算法,感觉效率还是蛮高的.(之后在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;}
原创粉丝点击