貪心+基礎動態規劃(dp)+簡單STL運用(棧.隊列)--(4)

多重揹包

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

快來練一練吧
http://acm.hdu.edu.cn/showproblem.php?pid=2191

https://www.acwing.com/problem/content/description/5/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章