多重背包及其优化

例题引入:

原题链接:https://www.acwing.com/problem/content/4/

有 N 种物品和一个容量是 V 的背包。

第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤100
0<vi,wi,si≤100
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10
简单介绍,这是多重背包模型的模板题,下面讨论如何解决;

直接思路:

这个跟完全背包很像,完全背包不限制每件物品取的次数,而多重背包规定了每次物品的次数,所以可以按照完全背包的写法来写。

状态表示: f[i][j]

集合:所有只考虑前i个物品,且总体积不超过j的所有选法;
属性:max;

状态计算:即集合的划分

这里就考虑第i个物品选几个,从0,1,2,3,…s[i];
代码实现:

朴素做法(n3):
#include<bits/stdc++.h>

using namespace std;

int v[110];//体积
int w[110];//价值
int s[110];//个数
int f[110][110];//前i个物品,总体积不超过j的最大价值
int n,m;
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d%d",&v[i],&w[i],&s[i]);
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=m;j++)
        {
            f[i][j]=f[i-1][j];//这个删去也可
            for(int k=0;k<=s[i];k++)
            {
                if(j>=k*v[i])//只有可以放得下的情况下才放
                    f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
            }
        }
    }
    printf("%d",f[n][m]);
    return 0;
}
优化成一维:
#include<bits/stdc++.h>

using namespace std;

int v[110];
int w[110];
int s[110];
int f[110];
int n,m;
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d%d",&v[i],&w[i],&s[i]);
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=m;j>=0;j--)//优化成一维注意这里的倒序
        {
            for(int k=0;k<=s[i];k++)
            {
                if(j>=k*v[i])
                    f[j]=max(f[j],f[j-k*v[i]]+k*w[i]);
           //对比来看!:f[i][j]=max(f[i-1][j],f[i]f[k-k*v[i]]+k*w[i]);
            }
        }
    }
    printf("%d",f[m]);
    return 0;
}
二进制优化(nvlogs)

这里介绍这个方法,当然这个方法是前人总结出来的,我们现在就是去理解然后掌握;
首先要考虑为什么要优化,因为数据量大的时候会超时;
其次,为什么不能按照完全背包的优化方式进行优化;
原因:
在这里插入图片描述

最后,为什么可以用二进制优化;
原因:
任何一个非负整数,可以用一个以1为首项,2为公比的等比数列中的数来表示,
例:10
可以用1,2,4,8,这几个数表示,
1:可以表示0~1;
1,2:表示0~3;
1,2,4:表示0~7;
1,2,4,8:表示0~15;
故可以表示10;
二进制如何优化的:
展现:那么这题如何优化呢,这题我们可以根据上方的推到,将其以方式分成若干组,logn组,但是这里我们只需且只可表示到不能大于其总数,即若表示10,那么一个分成,1,2,4,3,这四个组。
对一个数n,则分成1,2,4,…2k ,c(c<2k+1),并且应该:c+2k+1-1=n;
代码实现:

#include<bits/stdc++.h>

using namespace std;

int v[12000];
int w[12000];
int f[12000];
int n,m;
int main()
{
    scanf("%d%d",&n,&m);
    int cnt=0;//注意cnt要定义此范围的全局
    for(int i=1;i<=n;i++)
    {
        int a,b,s;
        scanf("%d%d%d",&a,&b,&s);
        int k=1;//以2的倍数增加
        while(k<=s)
        {
            cnt++;
            v[cnt] = a * k;//体积*个数
            w[cnt] = b * k;//价值*个数
            s -= k;//不断的减,直至s<k
            k *= 2;
        }
        if(s>0)//将剩下的存入
        {
            cnt++;
            v[cnt] = a * s;
            w[cnt] = b * s;
        }
    }
    n = cnt;
    for(int i=1;i<=n;i++)
    {
        for(int j=m;j>=v[i];j--)
        {
                f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
    }
    printf("%d\n",f[m]);
    return 0;
}

忘记说了,利用二进制优化之后,就是01背包了,因为,第i类物品个数被重新分组,分组以后,每个组可以选可以不选,全选就是这个组本来的数目,全不选那不就是0嘛,选其中几个等等,就会构成其原来0,1,2,3,…n;然后这个01背包是优化成一维的;

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值