0/1背包、完全背包、多重背包和混合三种背包问题

0/1背包:

问题 A: 0/1背包

时间限制: 1 Sec  内存限制: 128 MB
提交: 111  解决: 58
[提交][状态][讨论版][命题人: cbc]

题目描述

       一个旅行者有一个最多能用m公斤的背包,现在有n件物品,它们的重量分别是W1,W2,...,Wn,它们的价值分别为C1,C2,...,Cn.若每种物品只有一件求旅行者能获得最大总价值。 

输入

第1行:两个整数,M(背包容量,M<=200)和N(物品数量,N<=30);

第2..N+1行:每行二个整数Wi,Ci,表示每个物品的重量和价值。

输出

仅一行,一个数,表示最大总价值。

样例输入

10 4
2  1
3  3
4  5
7  9

样例输出

12
//方法一:二维数组
#include
#include
using namespace std;
int main()
{
	int M,N,w[35],c[35],f[35][205];//f集合表示前i个物品,总重量不超过M的旅行者能获得的最大总价值
	int i,j;
	cin>>M>>N;
	for(i=1;i<=N;i++)
		scanf("%d %d",&w[i],&c[i]);
	for(i=1;i<=N;i++)
	{
		for(j=1;j<=M;j++)
		{
			if(j>=w[i])
			f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+c[i]);
			else
			f[i][j]=f[i-1][j];
		}
	}
	printf("%d\n",f[N][M]);
	return 0;
}
//方法二:一维数组
#include
#include
using namespace std;
int main()
{
	int M,N,w[35],c[35];
    int f[205]={0};//f集合表示总重量不超过M的旅行者能获得的最大总价值
                    //f初值应为0
	int i,j;
	cin>>M>>N;
	for(i=1;i<=N;i++)
		scanf("%d %d",&w[i],&c[i]);
	for(i=1;i<=N;i++)
	{
		for(j=M;j>=w[i];j++)
		{
			f[j]=max(f[j],f[j-w[i]]+c[i]);
		}
	}
	printf("%d\n",f[M]);
	return 0;
}

完全背包问题:

问题 B: 完全背包问题

时间限制: 1 Sec  内存限制: 128 MB
提交: 98  解决: 60
[提交][状态][讨论版][命题人: cbc]

题目描述

设有n种物品,每种物品有一个重量及一个价值。但每种物品的数量是无限的,同时有一个背包,最大载重量为M,今从n种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于M,而价值的和为最大。

输入

第一行:两个整数,M(背包容量,M<=200)和N(物品数量,N<=30);

第2..N+1行:每行二个整数Wi,Ci,表示每个物品的重量和价值。

输出

仅一行,一个数,表示最大总价值。

样例输入

10  4
2  1
3  3
4  5
7  9

样例输出

max=12
#include
#include
using namespace std;
int main()
{
	int M,N,w[35],c[35],f[205]={0};
	int i,j;
	cin>>M>>N;
	for(i=1;i<=N;i++)
		scanf("%d %d",&w[i],&c[i]);
	for(i=1;i<=N;i++)
	{
		for(j=w[i];j<=M;j++)
		{
			f[j]=max(f[j],f[j-w[i]]+c[i]);
		}
	}
	printf("max=%d\n",f[M]);
	return 0;
}

这个算法使用一维数组,先看伪代码: for i=1..N for v=0..V f[v]=max{f[v],f[v-w[i]]+c[i]}; 你会发现,这个伪代码与01背包问题的伪代码只有v的循环次序不同而 已。为什么这样一改就可行呢?首先想想为什么01背包问题中要按照v=V..0 的逆序来循环。这是因为要保证第i次循环中的状态f[i][v]是由状态f[i-1][vw[i]]递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考 虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的 子结果f[i-1][v-w[i]]。而现在完全背包的特点恰是每种物品可选无限件,所 以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i 种物品的子结果f[i][v-w[i]],所以就可以并且必须采用v= 0..V的顺序循环。 这就是这个简单的程序为何成立的道理。 这个算法也可以以另外的思路得出。例如,基本思路中的状态转移方程 可以等价地变形成这种形式: f[i][v]=max{f[i-1][v],f[i][v-w[i]]+c[i]},将这个方程用一维数组实现,便得到了 上面的伪代码。

多重背包问题:

问题 C: 庆功会

时间限制: 1 Sec  内存限制: 128 MB
提交: 110  解决: 54
[提交][状态][讨论版][命题人: cbc]

题目描述

为了庆贺班级在校运动会上取得全校第一名成绩,班主任决定开一场庆功会,为此拨款购买奖品犒劳运动

员。期望拨款金额能购买最大价值的奖品,可以补充他们的精力和体力。

输入

第一行二个数n(n<=500),m(m<=6000),其中n代表希望购买的奖品的种数,m表示拨款金额。

接下来n行,每行3个数,v、w、s,分别表示第I种奖品的价格、价值(价格与价值是不同的概念)和购买的

数量(买0件到s件均可),其中v<=100,w<=1000,s<=10。

输出

第一行:一个数,表示此次购买能获得的最大的价值(注意!不是价格)。

样例输入

5 1000
80 20 4
40 50 9
30 50 7
40 30 6
20 20 1

样例输出

1040
//【解法一】朴素算法
//【参考程序】
#include
using namespace std;
int v[6002], w[6002], s[6002];
int f[6002];
int n, m;
int max(int x,int y) {
if (x < y) return y;
else return x;
}
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]<0) break;
                f[j] = max(f[j],f[j-k*v[i]]+k*w[i]);
            }
    printf("%d",f[m]);
    return 0;
} 
//【解法二】进行二进制优化,转换为01背包
//【参考程序】
#include
int v[10001],w[10001];
int f[6001];
int n,m,n1;
int max(int a,int b){
return a>b?a:b; //这句话等于:if (a>b) return a; else return b;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        int x,y,s,t=1;
        scanf("%d%d%d",&x,&y,&s);
        while (s>=t)
        {
            v[++n1]=x*t; //相当于n1++; v[n1]=x*t;
            w[n1]=y*t;
            s-=t;
            t*=2;
        }
        v[++n1]=x*s;
        w[n1]=y*s; //把s以2的指数分堆:1,2,4,…,2^(k-1),s-2^k+1,
    }
    for(int i=1;i<=n1;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;
}

         基本算法: 这题目和完全背包问题很类似。基本的方程只 需将完全背包问题的方程略微一改即可,因为对于第 i种物品有n[i]+1种策略:取0件,取1件……取n[i]件 。令f[i][v]表示前i种物品恰放入一个容量为v的背包 的最大权值,则:f[i][v]=max{f[i-1][v-k*w[i]]+ k*c[i]|0<=k<=n[i]}。复杂度是O(V*∑n[i])。

           转化为01背包问题 另一种好想好写的基本方法是转化为01背包求解:把第i种物品换成n[i]件01背包 中的物品,则得到了物品数为∑n[i]的01背包问题,直接求解,复杂度仍然是 O(V*∑n[i])。 但是我们期望将它转化为01背包问题之后能够像完全背包一样降低复杂度。仍 然考虑二进制的思想,我们考虑把第i种物品换成若干件物品,使得原问题中第i种物 品可取的每种策略——取0..n[i]件——均能等价于取若干件代换以后的物品。另外, 取超过n[i]件的策略必不能出现。 方法是:将第i种物品分成若干件物品,其中每件物品有一个系数,这件物品的费 用和价值均是原来的费用和价值乘以这个系数。使这些系数分别为 1,2,4,...,2^(k1),n[i]-2^k+1,且k是满足n[i]-2^k+1>0的最大整数(注意:这些系数已经可以组合出 1~n[i]内的所有数字)。例如,如果n[i]为13,就将这种物品分成系数分别为1,2,4,6的四 件物品。 分成的这几件物品的系数和为n[i],表明不可能取多于n[i]件的第i种物品。另外这 种方法也能保证对于0..n[i]间的每一个整数,均可以用若干个系数的和表示,这个证 明可以分0..2^k-1和2^k..n[i]两段来分别讨论得出,并不难,希望你自己思考尝试一 下。 这样就将第i种物品分成了O(logn[i])种物品,将原问题转化为了复杂度为 O(V*∑logn[i])的01背包问题,是很大的改进。

混合三种背包问题:

问题 D: 混合背包

时间限制: 1 Sec  内存限制: 128 MB
提交: 71  解决: 43
[提交][状态][讨论版][命题人: cbc]

题目描述

一个旅行者有一个最多能用V公斤的背包,现在有n件物品,它们的重量分别是W1,W2,...,Wn,它们的价值分别为C1,C2,...,Cn。有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

输入

第一行:二个整数,V(背包容量,V<=200),N(物品数量,N<=30);

第2..N+1行:每行三个整数Wi,Ci,Pi,前两个整数分别表示每个物品的重量,价值,第三个整数若为0,则说

明此物品可以购买无数件,若为其他数字,则为此物品可购买的最多件数(Pi)。

输出

仅一行,一个数,表示最大总价值。

样例输入

10 3
2  1  0
3  3  1
4  5  4

样例输出

11
#include
#include
using namespace std;
int main()
{
	int M,N,w[35],c[35],s[35],f[205]={0};
	int i,j,k;
	cin>>M>>N;
	for(i=1;i<=N;i++)
		scanf("%d %d %d",&w[i],&c[i],&s[i]);
	for(i=1;i<=N;i++)
	{
		if(s[i]==0)//完全背包问题
		{
			for(j=w[i];j<=M;j++)
				f[j]=max(f[j],f[j-w[i]]+c[i]);
		}
		else//0/1背包问题和多重背包问题
		{
			for(j=M;j>=0;j--)
			{
				for(k=1;k<=s[i];k++)
				{
					if(j-k*w[i]<0) break;
					else
					f[j]=max(f[j],f[j-k*w[i]]+k*c[i]);
				}
			}
		}
	}
	printf("%d\n",f[M]);
	return 0;
}

           如果将01背包、完全背包、多重背包混合起来。也就是说,有的物品只可以取一 次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个 上限(多重背包)。应该怎么求解呢?

01背包与完全背包的混合

考虑到在01背包和完全背包中最后给出的伪代码只有一处不同,故如果只有两类物 品:一类物品只能取一次,另一类物品可以取无限次,那么只需在对每个物品应用转 移方程时,根据物品的类别选用顺序或逆序的循环即可,复杂度是O(VN)。

你可能感兴趣的:(0/1背包、完全背包、多重背包和混合三种背包问题)