多重揹包
视频讲解<<](https://www.bilibili.com/video/av84042870?from=search&seid=8681181514824614451)
https://www.bilibili.com/video/av84042870?from=search&seid=8681181514824614451
ACM题目链接
https://vjudge.net/contest/353904#overview
题目描述
有N种物品和一个容量为V的揹包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入揹包可使这些物品的费用总和不超过揹包容量,且价值总和最大。 |
基本算法:这题目和完全揹包问题很像。基本的方程只需将完全揹包问题的方程略微一改即可,因为对于第i种物品有n[i]+1种策略:取0件,取1件,取2件…取n[i]件。令f[i][v]表示前i种物品恰放入一个容量为v的揹包的最大权值,则有
状态转移方程:
=========f[i][v]=max(f[i-1][v-k *c[i]]+k *w[i]|0<=k<=n[i])
基本思路
#include<iostream>
#include<algorithm>
using namespace std;
const int MAX=1010;
int N,V;
int f[MAX];
int c[MAX],w[MAX],s[MAX]; //费用和价值
int main()
{
cin>>N>>V;
for(int i=1;i<=N;i++)
cin>>c[i]>>w[i]>>s[i];
for(int i=1;i<=N;i++)//N件物品
{
for(int v=V;v>=c[i];v--)//揹包容量——所剩费用,和01揹包一样,从V到0
{
for(int k=1;k<=s[i]&&k*c[i]<=v;k++)
f[v]=max(f[v],f[v-k*c[i]]+k*w[i]);
}
}
cout<<f[V]<<endl;//为什么直接输出f[v]不用找最大的,看第一讲的初始化说明
}
其中c[i]是物品的数量,和完全揹包的不同支出在于完全揹包可以取无数件,而多重揹包给定了最多能取的数量。这样也是三个循环,分别是揹包容量,物品个数和物品种类。这样如果数量比较多的情况,很明显这个做法也会超时,所以我们也要想更优化的方法去完善。
转化为01揹包问题
转化为01揹包求解:把第i种物品换成n[i]件01揹包中的物品。考虑二进制的思想,考虑把第i种物品换成若干件物品,使得原问题中第i种物品可取的每种策略——取0…n[i]件——均能等价于取若干件代换以后的物品。另外,取超过n[i]件的策略必不能出现。
方法是:将第i种物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数。使这些系数分别为1,2,4,…,2(k-1),n[i]-2k+1,且k是满足n[i]-2^k+1>0的最大整数。例如,如果n[i]为13,就将这种物品分成系数分别为1,2,4,6的四件物品。
分成的这几件物品的系数和为n[i],表明不可能取多于n[i]件的第i种物品。另外这种方法也能保证对于0…n[i]间的每一个整数,均可以用若干个系数的和表示,证明我也没看过这里就不贴上了,主要还是需要去理解代码,代码在下面给出。
五、多重揹包代码:
1.二进制优化
#include <bits/stdc++.h>
const int maxn=1e6+11;
using namespace std;
int w[maxn],v[maxn];
int dp[2020],t;//t用来表示物品种类
int main()
{
int v1,w1,bagv,n,num;
cin>>n>>bagv;
for(int i=1;i<=n;i++)
{
cin>>w1>>v1>>num;
int k=1;
while(k<=num)
{
//转化成二进制,k表示将价值为v1体积为w1的物品数量为num的物品分成k个
v[++t]=k*v1;
w[t]=k*w1;
num-=k;k*=2;
}
if(num>0)
{
//如果num大于0还剩下的num为系数
v[++t]=v1*num;
w[t]=w1*num;
}
}
//然后就是转化为0 1揹包了
for (int i = 1; i <=t; ++i)
{
for(int j=bagv;j>=w[i];j--)
{
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}
cout<<dp[bagv]<<endl;
return 0;
}
2.
#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <cstring>
#define MAX 1000000
using namespace std;
int dp[MAX];//存储最后揹包最大能存多少
int value[MAX],weight[MAX],number[MAX];//分别存的是物品的价值,每一个的重量以及数量
int bag;
void ZeroOnePack(int weight,int value )//01揹包
{
int i;
for(i = bag; i>=weight; i--)
{
dp[i] = max(dp[i],dp[i-weight]+value);
}
}
void CompletePack(int weight,int value)//完全揹包
{
int i;
for(i = weight; i<=bag; i++)
{
dp[i] = max(dp[i],dp[i-weight]+value);
}
}
void MultiplePack(int weight,int value,int number)//多重揹包
{
if(bag<=number*weight)//如果总容量比这个物品的容量要小,那么这个物品可以直到取完,相当于完全揹包
{
CompletePack(weight,value);
return ;
}
else//否则就将多重揹包转化为01揹包
{
int k = 1;
while(k<=number)
{
ZeroOnePack(k*weight,k*value);
number = number-k;
k = 2*k;//这里采用二进制思想
}
ZeroOnePack(number*weight,number*value);
}
}
int main()
{
int n;
while(~scanf("%d%d",&bag,&n))
{
int i,sum=0;
for(i = 0; i<n; i++)
{
scanf("%d",&number[i]);//输入数量
scanf("%d",&value[i]);//输入价值 此题没有物品的重量,可以理解为体积和价值相等
}
memset(dp,0,sizeof(dp));
for(i = 0; i<n; i++)
{
MultiplePack(value[i],value[i],number[i]);//调用多重揹包,注意穿参的时候分别是重量,价值和数量
}
cout<<dp[bag]<<endl;
}
return 0;
}