1、0-1揹包问题
揹包问题的典型描述如下:
一、假如你是奥特曼,有N点的攻击力,现在有一些小怪兽,每一个小怪兽被打倒都需要消耗你一定数量的攻击力,同时你将获得该怪兽对应的金币。现在请计算你所能获得的最多金币数。
二、加入你是小偷,带了一个容量为V的包,此时你去商店偷商品(每一个商品都具有不同的价值),每偷一个商品将占用你揹包一定的空间,请你偷价值尽量多的商品。
2、问题的分析
这一类问题都是0-1揹包问题,换句话说,每一个问题中都可以抽象为“偷”或者“不偷”,以此来达到目标函数最大化。这种问题的求解通常使用动态规划。
假设已知x个商品它们的体积存储在数组L中;
假设已知x个商品它们的价值存储在数组P中;
假设揹包最大容积是V,那么我们的状态函数就是B(x,V)——表示从x个商品中偷使得获得价值最多,此时的揹包容积为V。
现在我们可以从后往前推:
如果最后一个商品可以被偷(包还装得下),那么一定偷这个商品(多偷一个是一个,这是常识)
如果最后一个商品的体积大于揹包剩下的容积,显然我们已经无法偷这个商品,那么此时的状态函数为B(x-1,V)
现在剩下一个新的问题,那就是剩下的倒数第二个商品是偷好,还是不偷好,也就是偷和不偷哪个可以使最终的收益最大?
一、偷,那么此时的状态函数为B(x-1,V-L[x-1])+P[x-1]
二、不偷,那么此时的状态函数B(x-1,V)
因此,通过上述分析,我们就知道了状态的转移函数,那么一直运算到边界条件,就可以返回求出最优解,这是动态规划中典型的分而治之的思想,也就是把大问题分解成若干小问题求解,最终获得最优解。
为了避免同一个子问题的反复求解,我们一般使用记忆化递归的方法,记录每一个子问题的解,然后记录在一个数组中,这样可以控制算法的时间空间复杂度。
3、编程实现
loupan, zijin = map(int,input().split())
L = [0]
P = [0]
for i in range(loupan):
l, p = map(int,input().split())
L.append(l)
P.append(p)
B = [[0]*(zijin+1)]*(loupan+1)
print("B:",B)
print("L:",L)
print("P:",P)
for c in range(1,loupan+1):
for r in range(1,zijin+1):
if r < L[c]:
B[c][r] = B[c-1][r]
else:
B[c][r] = max(B[c-1][r],B[c-1][r-L[c]] + P[c])
print(B[loupan][zijin])