背包那些经典的例子( 下 )

补上我的上篇讲解01背包与完全背包,这一篇讲解两个我认为比较经典的背包题。一个是多重背包题,直接套模板可以ac,一个是多重背包与完全背包题,需要改变一下模板。当然只要理解了背包的核心就能轻松改动模板。道可道,非常道,这个核心我还是模糊到难以讲出来。

多重背包问题—Coin HDU - 2844

简单说一下题目:
有件商品最大价格不超过m, 然后你有很多种价值不同的硬币,且数目有限。题目问你,在1到m中的那些价格中,求手中的硬币刚好能够支付的价格(硬币价值只有刚好等于某个价格时候才行,超过了就不满足题意了)。

题目思路:
如果对多重背包了解的,可以很快想出来该题的解题算法,就是一个标准的套模板题嘛。我们看看一般的多重背包题意。

有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值 是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

细细品读后,发现这个跟本题的意思基本一样。多重背包问题,一个有限容量的背包要装下最大价值的物品,眼前由很多种物品,有一定的价值和体积,但是呢每种物品数量又是有限的。这个跟完全背包有点不同,完全背包是同种物品数量无限
说到这,可以知道这是一个简单的多重背包题了吧,多重背包由基础背包与完全背包组成,一个模板就能解决这一问题。当然死记模板也是不好。

下面看看代吧。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100010;
//把一种物品的花费cost,价值weight,数目amount放在一个结构体里绑定起来,方便调用
//当然使用数组也行,这一题,用不到cost的值,不写也行。
struct Pack
{
    int cost;
    int weight;
    int amount;
}pack[maxn];
//状态转移的关键数组f[],和背包的总容量V(也就是本题的最大价值)
int f[maxn], V;
//基础背包
void Basepack(int cost, int weight)
{
    for(int v=V; v>=cost; v--)
        f[v] = max(f[v], f[v-cost]+weight);
}
//完全背包
void Competepack(int cost, int weight)
{
    for(int v=cost; v<=V; v++)
        f[v] = max(f[v], f[v-cost]+weight);
}
//多重背包,前两个背包的组合
void Multiplepack(int cost, int weight, int amount)
{
    //当某一种物品的总消耗体积大于背包的总容量,可以间接地视作是完全背包问题
    //你可以把它当作是,这种物品是无限数目的。
    if(cost*amount >= V)
    {
        Competepack(cost, weight);
        return ;
    }
    //不是完全背包就是基础背包了
    for(int k=1; k<amount; k<<=1)
    {
        Basepack(k*cost, k*weight);
        amount -= k;
    }
    Basepack(amount*cost, amount*weight);
}
int main()
{
    //是消除c++的输入输出的不必要的操作,减少时间,感觉没啥用,有的时候用c++输出照样超时。
    ios::sync_with_stdio(false);
    int n;
    while(cin>>n>>V && n+V)
    {
        for(int i=0; i<n; i++) cin>>pack[i].weight;
        for(int i=0; i<n; i++) cin>>pack[i].amount;

        //对f数组初始化,可以直接用memset
        for(int i=0; i<=V; i++) f[i] = 0;
        //这里要看清题意,物品的消耗是价值,而求的也是价值,于是cost的位置被weight占了
        for(int i=0; i<n; i++)
            Multiplepack(pack[i].weight, pack[i].weight, pack[i].amount);
        //求1toV中有哪些价格刚好支付得了。    
        int ans = 0;
        for(int i=1; i<=V; i++)
            if(f[i] == i) ans++;

        cout<<ans<<endl;
    }
    return 0;
}

混合背包问题——The trouble of Xiaoqian HDU - 3591

这个输入输出格式就不多说了,自己找链接看吧。

思路

细看题发现似乎是背包题,而且是多重背包题,但是又发现有一个收银员很碍事。这个其实就是混合背包问题。一个题目里面有两个背包问题。

首先是顾客的硬币数做一个背包问题,是多重背包。然后收银员找的硬币数,因硬币数无限,看作是完全背包问题。

依据题意定义子问题f1[i]表示支付i价值的硬币的最小数量,而f2[i]表示找i价值的硬币的最小数量。

那么怎么求最小流动的硬币数量。
当支付的硬币总价值为k,k>=V。那么f1[k]就是支付的最小硬币数量,那么找的硬币的最小数量就是f2[k-V].

讲解可能不清楚,那就看看下面的代码自己体会一下吧。

#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define maxn 20010
struct Pack
{
    int cost;
    int weight;
    int amount;
}pack[maxn];
//顾客的硬币数状态f1, 收银员找硬币数目的状态
int f1[maxn], f2[maxn], V;
//因为题目求的是最小的硬币数目,那么就得稍微修改一下摸板,f[]不再是求最大价值了,而是求最小硬币数目
//max 变为 min ,然后是相加硬币数目+amount 而不是加价值weight了,故而得额外加一个参数
//这里最大范围不在局限于V了,因为价格可以超出,我们把范围就定为比较大的maxn。
void Basepack(int cost, int weight, int amount, int* f)
{
    for(int v=maxn; v>=cost; v--)
        f[v] = min(f[v], f[v-cost]+amount);
}
//完全背包,比较的时候每次只加1个硬币数,主要是完全背包是由上一个状态推下一状态,
//上一个状态是k个硬币数,那么下一个状态是k+1个数目了。弄懂二维转化为一维数组就能容易明白这个道理了。
void Competepack(int cost, int weight, int* f)
{
    for(int v=cost; v<=maxn; v++)
        f[v] = min(f[v], f[v-cost]+1);
}

void Multiplepack(int cost, int weight, int amount ,int* f)
{
    if(cost*amount >= V)
    {
        Competepack(cost, weight, f);
        return ;
    }
    for(int k=1; k<amount; k<<=1)
    {
        Basepack(k*cost, k*weight, k, f);
        amount -= k;
    }
    Basepack(amount*cost, amount*weight, amount, f);
}
int main()
{
    int n, cas = 1;
    while(cin>>n>>V && n+V)
    {
        for(int i=0; i<n; i++) cin>>pack[i].weight;
        for(int i=0; i<n; i++) cin>>pack[i].amount;

       //求最小状态,初始化为无穷大,自定义的无穷大INF
        memset(f1, INF, sizeof(f1));
        memset(f2, INF, sizeof(f2));
        f1[0] = f2[0] = 0;
        //对顾客支付的硬币,多重背包处理
        for(int i=0; i<n; i++) Multiplepack(pack[i].weight, pack[i].weight, pack[i].amount, f1);
        //对于收银员的硬币, 完全背包处理
        for(int i=0; i<n; i++) Competepack(pack[i].weight, pack[i].weight, f2);

        //找到最小的流动硬币数量
        int ans = INF;
        for(int i=V; i<=maxn; i++)
            ans = min(ans, f1[i]+f2[i-V]);
        if(ans == INF)ans = -1;
        printf("Case %d: %d\n", cas++, ans);
    }
    return 0;
}

经典的列题就讲这四个吧,理解后能更深刻地了解三个背包了,也能更灵活地使用模板了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值