背包问题总结一

今天做数论的题目时,遇到一道多重背包的问题。好久没做过背包了,一时有点迷糊,当时理解的也不是很透彻,果断把背包九讲重新看了一遍。这里做下总结,加深自己的理解。

背包问题求的是在花费一定代价(物品的重量或体积)下,一个背包装入物品后所获得的最大价值。总的包括三种基本的背包:01背包,完全背包,多重背包。还有由这三种背包延伸出来的问题:混合背包,二维费用的背包,分组背包,背包问题问法的变化等。

01背包是最基本的背包,有N种物品,第i件物品的花费是c[i],价值是w[i],每种物品只有一件,求将哪些物品装入背包获得的价值最大。

完全背包,有N种物品,第i件物品的花费是c[i],价值是w[i],每种物品有无限件,求将哪些物品装入背包使这些物品的费用不超过背包容量,且总价值最大。

多重背包,有N种物品,第i件物品的花费是c[i],价值是w[i],每种物品有有限件为n[i],求将哪些物品装入背包使这些物品的费用不超过背包容量,且总价值最大。

可见,三种背包的不同之处是物品的数目不一样。其实完全背包和多重背包都可以转化为01背包求解。

01背包

因为每种物品只有一件,有选和不选两种可能。设f[i][v]表示前i种物品恰好放入容量为v的背包的最大价值。那么可以得到状态转移方程f[i][v] = max(f[i-1][v], f[i-1][v-c[i]]+w[i]),即第i种物品不放为f[i-1][v],放为f[i-1][v-c[i]]+w[i],取较大值。其时间复杂度为O(nv)。

其实我们可以用一个一维数组表示上述状态的转移f[v],它与f[i][v]的意义相同。我们可以得到01背包如下:

void ZeroOnePack()
{
	for(int i = 1; i <= N; i++)
	{
		for(int v = V; v >= c[i]; v--)
			f[v] = max(f[v],f[v-c[i]]+w[i]);
	}
}

外循环很容易理解,循环N次代表N种物品,内循环是背包容量,f[v]对应f[i-1][v],f[v-c[i]]+w[i]对应f[i-1][v-c[i]]+w[i]。但注意是逆序。因为f[v]是从上一个状态推出来的,上一个状态是f[i-1][v]或f[i-1][v-c[i]]+w[i],即还没有放当前物品。如果是按顺序的话,意味着f[i][v]是由f[i][v-c[i]]推出来,显然与01背包每件物品只有一件相违背。

初始化问题:有的题目要求恰好装满背包,而有的没有要求恰好装满,它们在初始化上有细微差别。

如果要求恰好装满背包,那么f[0]初始为0,f[1...V]初始为-INF(如果求最大价值),最小价值初始为-INF,初始化就是在没有放任何物品时背包的合法状态,因为只有背包容量为0的时候,恰好有价值为0的nothing"恰好装满",它属于合法状态,其余均不是。

如果没有要求必须装满,f[0...V]初始化为0,因为任何容量的背包都有一个合法状态,就是不放入物品。

这里的初始化也适用于以下的背包问题。

完全背包

与01背包不同的是这里的每种物品都有无限件,每种物品就不是放与不放的问题了,而是放0个,1个.....v/c[i]个的问题。子状态f[i][v]定义为前i种物品恰好装入容量为v的背包的最大价值。那么状态转移方程为

f[i][v] = max{f[i-1][v-k*c[i]]+k*w[i] | 0 <= k <= v/c[i]}。

它的时间复杂度不是O(NV),而是O(V*∑(V/c[i]) )。

完全背包可以转化为01背包求解,这里有一个O(NV)的算法:

void CompletePack()
{
	for(int i = 1; i <= N; i++)
	{
		for(int v = c[i]; v <= V; v++)
			f[v] = max(f[v],f[v-c[i]]+w[i]);
	}
}

可以发现,与01背包只是内循环的顺序不同。为什么这里是顺序呢?想一想01背包之所以逆序是因为推f[v]时必须是上一个状态,即没放第i种物品的状态,它保证了每种物品要么选要么不选。而这里每种物品有无限件,当我们加入当前第i种物品进背包的时候,可能正需要已经放入第i种物品的这样一个状态f[i][v-c[i]]。所以这里是顺序。

多重背包

多重背包中的每种物品的数目有一定的界限n[i]。每种物品可以放0个,1个......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]}。

复杂度为O(V*∑n[i])。

转化为01背包求解,就是将第i种物品分成n[i]件01背包中的物品。得到了物品数是∑n[i]件的01背包。像这样:

void MultiplePack()
{
	for(int i = 1; i <= N; i++)
	{
		for(int j = 1; j <= item[i].num; j++)
		{
			for(int v = V; v >= item[i].c; v--)
				f[v] = max(f[v],f[v-item[i].c]+item[i].w);
		}
	}
}

注意内两层循环不能颠倒顺序,否则就是分组背包了。其复杂度仍然是O(V*∑n[i])。

还有一种复杂度为O(V*∑(log n[i]))的算法,利用的是二进制的思想。就是将第i种物品分成若干件物品,其中每件物品都有一个系数,这件物品的费用和价值都是原来的费用和价值乘以这个系数。这些系数分别是1,2,4...2^(k-1),n[i]-2^(k)+1,其中k是满足n[i]-2^k+1>0的最大整数。例如原来n[i] = 13,那么该种物品可以拆分成4种物品,其系数分别是1,2,4,6。而且可以证明小于等于n[i]的任何数目的物品都可以由拆分后物品的和组成。这样就减低了复杂度。具体代码为:

void ZeroOnePack(int cost, int weight)
{
	for(int v = V; v >= cost; v--)
		f[v] = max(f[v],f[v-cost]+weight);
}
void CompletePack(int cost, int weight)
{
	for(int v = cost; v <= V; v++)
		f[v] = max(f[v],f[v-cost]+weight);
}

void MultiplePack()
{
	for(int i = 1; i <= N; i++)
	{
		if(num[i] * c[i] >= V)
		{
			CompletePack(c[i],w[i]); //相当于第i种物品有无限件,可以直接完全背包。
		}
		else
		{ //转化成若干个物品,进行01背包
			int k = 1;
			while(k < amount)
			{
				ZeroOnePack(k*c[i],k*w[i]);
				amount -= k;
				k = k << 1;
			}
			ZeroOnePack(amount*c[i],amount*w[i]);
		}
	}
}

多重背包问题中还有一类,给出每种物品的价值和数目,问这些物品的组合能否到达某个价值。即判断是否可达的问题。解决这个问题,经常用一个数组use[i]记录i种物品被用的次数。具体见例题判断是否可达的问题

背包问题总结一

时间: 2024-08-13 15:59:09

背包问题总结一的相关文章

砝码问题之二(完全背包问题)

有一组砝码,重量互不相等,分别为m1.m2.m3……mn:每种砝码的数量有无限个. 现要用这些砝码去称物体的重量,给你一个重量n,请你判断有给定的砝码能否称出重量n. 现在给你一个正整数列表w和一个正整数n,列表w中的第i个元素w[i]表示第i种砝码的重量, n表示要你判断的重量.如果给定砝码能称出重量n,输出Yes,否则输出No. 例如,w=[2,5,11], n=9,则输出Yes(取两个2,一个5). w = [2, 5, 11] n = 9 S = [-1 for i in xrange(

NYOJ 106 背包问题

背包问题 时间限制:3000 ms  |  内存限制:65535 KB 难度:3 描述 现在有很多物品(它们是可以分割的),我们知道它们每个物品的单位重量的价值v和重量w(1<=v,w<=10):如果给你一个背包它能容纳的重量为m(10<=m<=20),你所要做的就是把物品装到背包里,使背包里的物品的价值总和最大. 输入 第一行输入一个正整数n(1<=n<=5),表示有n组测试数据: 随后有n测试数据,每组测试数据的第一行有两个正整数s,m(1<=s<=10

UESTC 31 饭卡(Card) --背包问题

背包问题. 思路:如果m<5,此时也不能消费,所以此时答案为m m>=5: 求出背包容量为m-5,买前n-1样便宜的菜(排个序)的最大价值(即最大消费,即消费完后剩余值最接近5)最后减去最大的那个菜的价格,就得到最小的余额. 代码: #include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <algorithm> using n

动态规划之01背包问题(最易理解的讲解)

01背包问题,是用来介绍动态规划算法最经典的例子,网上关于01背包问题的讲解也很多,我写这篇文章力争做到用最简单的方式,最少的公式把01背包问题讲解透彻. 01背包的状态转换方程 f[i,j] = Max{ f[i-1,j-Wi]+Pi( j >= Wi ),  f[i-1,j] } f[i,j]表示在前i件物品中选择若干件放在承重为 j 的背包中,可以取得的最大价值. Pi表示第i件物品的价值. 决策:为了背包中物品总价值最大化,第 i件物品应该放入背包中吗 ? 题目描述: 有编号分别为a,b

0-1背包问题

[问题] 有一个贼在偷窃一家商店时发现有N件物品:第i件物品值pi元,重wi磅(1≤i≤N),且都是整数. 他希望带走的东西越值钱越好,但他的背包中最多能装下M磅的东西(整数). 如果每件物品或被带走或被留下,小偷应该带走哪几样东西? [算法解析] 令f(i,y) 表示容量为y,物品i,i+1,···,n 的优化效益值,按优化原理可列递归关系如下: 初始背包问题的递归方程 f(1,c)=max{f(2,c), f(2,c-w1)+p1} 迭代计算从f(n, *)开始((1)式)然后应用(2)式递

如何理解背包问题

问题 假定背包的最大容量为W,N件物品,每件物品都有自己的价值和重量,将物品放入背包中使得背包内物品的总价值最大.   背包问题wiki 可以想象这样一个场景--小偷在屋子里偷东西,他带着一只背包.屋子里物品数量有限--每件物品都具有一定的重量和价值--珠宝重量轻但价值高,桌 子重但价值低.最重要的是小偷背包容量有限.很明显,他不能把桌子分成两份或者带走珠宝的3/4.对于一件物品他只能选择带走或者不带走. 示例: Knapsack Max weight : W = 10 (units) Tota

1616 疯狂的采药(完全背包问题)

难度:普及- 题目类型:动规 提交次数:1 涉及知识:背包动规 题目背景 此题为NOIP2005普及组第三题的疯狂版. 此题为纪念LiYuxiang而生. 题目描述 LiYuxiang是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师.为此,他想拜附近最有威望的医师为师.医师为了判断他的资质,给他出了一个难题.医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同种类的草药,采每一种都需要一些时间,每一种也有它自身的价值.我会给你一段时间,在这段时间里,你可以采到一些草药.如

背包问题

给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高. 使用动态规划算法解答 1 import java.util.Scanner; 2 3 public class 一维数组解背包问题 { 4 public static void main(String[] args) { 5 Scanner sc = new Scanner(System.in); 6 int W = sc.nextInt(), n = sc.nextInt();//W=背包容

bzoj3163: [Heoi2013]Eden的新背包问题

Description “寄没有地址的信,这样的情绪有种距离,你放着谁的歌曲,是怎样的心心静,能不能说给我听.”失忆的Eden总想努力地回忆起过去,然而总是只能清晰地记得那种思念的感觉,却不能回忆起她的音容笑貌. 记忆中,她总是喜欢给Eden出谜题:在 valentine’s day 的夜晚,两人在闹市中闲逛时,望着礼品店里精巧玲珑的各式玩偶,她突发奇想,问了 Eden这样的一个问题:有n个玩偶,每个玩偶有对应的价值.价钱,每个玩偶都可以被买有限次,在携带的价钱m固定的情况下,如何选择买哪些玩偶

01背包问题:POJ3624

背包问题是动态规划中的经典问题,而01背包问题是最基本的背包问题,也是最需要深刻理解的,否则何谈复杂的背包问题. POJ3624是一道纯粹的01背包问题,在此,加入新的要求:输出放入物品的方案. 我们的数组基于这样一种假设: totalN表示物品的种类,totalW表示背包的容量 w[i]表示第i件物品的重量,d[i]表示第i件物品的价值. F(i,j)表示前i件物品放入容量为j的背包中,背包内物品的最大价值. F(i,j) = max{ F(i-1,j) , F(i-1,j-w[i])+d[i