贪心+基础动态规划(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/

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