多重揹包
視頻講解<<](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;
}