动态规划——背包问题(1)


在这里插入图片描述

背包问题概述

给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。

在这里插入图片描述

0-1背包

定义:

01背包是在M件物品取出若干件放在空间为W的背包里,每件物品的体积为W1,W2至Wn,与之相对应的价值为P1,P2至Pn。01背包是背包问题中最简单的问题。01背包的约束条件是给定几种物品,每种物品有且只有一个,并且有权值和体积两个属性。在01背包问题中,因为每种物品只有一个,对于每个物品只需要考虑选与不选两种情况。如果不选择将其放入背包中,则不需要处理。如果选择将其放入背包中,由于不清楚之前放入的物品占据了多大的空间,需要枚举将这个物品放入背包后可能占据背包空间的所有情况。

例题

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8
在这里插入图片描述

分析

N = 1010

v = [0] * N
w = [0] * N
dp = [[0] * N for _ in range(N)]

n, m = map(int, input().split())
for i in range(1, n + 1) :
	v[i], w[i] = map(int, input().split())

for i in range(1, n + 1) :
	for j in range(1, m + 1) :
		if j - v[i] >= 0 :
			dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i])
		else :
			dp[i][j] = dp[i - 1][j]

print(dp[n][m])

滚动数组优化

N = 1010

v = [0] * N
w = [0] * N
dp = [0] * N

n, m = map(int, input().split())
for i in range(1, n + 1) :
	v[i], w[i] = map(int, input().split())

for i in range(1, n + 1) :
	for j in range(m, v[i] - 1, - 1) :
		dp[j] = max(dp[j], dp[j - v[i]] + w[i])
print(dp[m])

下面来说一下为何使用滚动数组时,遍历背包的顺序有倒着来。
因为动态规划讲究一个顺序问题:一定要用已更新的状态来更新现有状态。
0-1背包是用上一层(即前一个物品的状态)来更新现有物品状态,如果使用顺序遍历,那么dp[j - v[i]]所涉及的状态就是当前层级的状态,也就是说物品可能被多次选择。不满足0-1背包要求。

完全背包

定义

有N种物品和一个容量为V的背包,每种物品都有无限件可用。
第i种物品的体积是c,价值是w。求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。

例题

有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。

第 i 种物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10

分析
在这里插入图片描述

N = 1010

v = [0] * N
w = [0] * N
dp = [[0] * N for _ in range(N)]

n, m = map(int, input().split())
for i in range(1, n + 1) :
	v[i], w[i] = map(int, input().split())
for i in range(1, n + 1) :
	for j in range(1, m + 1) :
		dp[i][j] = dp[i - 1][j]
		if j - v[i] >= 0 :
			dp[i][j] = max(dp[i - 1][j], dp[i][j - v[i]] + w[i])
print(dp[n][m])

滚动数组优化

N = 1010

v = [0] * N
w = [0] * N
dp = [0] * N

n, m = map(int, input().split())

for i in range(1, n + 1) :
	v[i], w[i] = map(int, input().split())

for i in range(1, n + 1) :
	for j in range(v[i], m + 1) :
		dp[j] = max(dp[j], dp[j - v[i]] + w[i])
print(dp[m])

因为dp[i][j] = max(dp[i - 1][j], dp[i][j - v[i]] + w[i])需要用到当前层级来更新背包(也就是物品可重复取),所以在使用滚动数组优化时需要从前往后顺序遍历

多重背包

定义

有N种物品和一个容量为V 的背包。第i种物品最多有Mi件可用,每件耗费的空间是Ci ,价值是Wi 。求解将哪些物品装入背包可使这些物品的耗费的空间总和不超过背包容量,且价值总和最大。

例题

有 N 种物品和一个容量是 V 的背包。

第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤100
0<vi,wi,si≤100
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10
分析
在这里插入图片描述

N = 110

v = [0] * N
w = [0] * N
s = [0] * N
dp = [[0] * N for _ in range(N)]
n, m = map(int, input().split())

for i in range(1, n + 1) :
	v[i], w[i], s[i] = map(int, input().split())

for i in range(1, n + 1) :
	for j in range(0, m + 1) :
		for k in range(s[i] + 1) :
			if j >= v[i] * k :
				dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i] * k]  + w[i] * k)

print(dp[n][m])

二进制优化

一个物品具有M个,可以拆解为1,2,2^2 … 2^n-1, M - (2 ^ n - 1)这些数,这些数的组合可以保证能选到0~M的物品

N = 1010

f = [0] * N

n, m = map(int, input().split())

for i in range(1, n + 1) :
    v, w, s = map(int, input().split())
    k = 1
    while k <= s :
        for j in range(m, k * v - 1, -1) :
            f[j] = max(f[j], f[j - k * v] + k * w)
        s -= k
        k *= 2
    if s > 0 :
        for j in range(m, s * v - 1, -1) :
            f[j] = max(f[j], f[j - s * v] + s * w)
print(f[m])

分组背包

定义

有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

例题

有 N 组物品和一个容量是 V 的背包。

每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。

求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。

输出最大价值。

输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。

接下来有 N 组数据:

每组数据第一行有一个整数 Si,表示第 i 个物品组的物品数量;
每组数据接下来有 Si 行,每行有两个整数 vij,wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤100
0<Si≤100
0<vij,wij≤100
输入样例
3 5
2
1 2
2 4
1
3 4
1
4 5
输出样例:
8

分析
在这里插入图片描述

N = 110

v = [[0] * N for _ in range(N)]
w = [[0] * N for _ in range(N)]
s = [0] * N
dp = [0] * N

n, m = map(int, input().split())

for i in range(1, n + 1) :
	s[i] = int(input())
	for j in range(s[i]) :
		v[i][j], w[i][j] = map(int, input().split())

for i in range(1, n + 1) :
	for j in range(m, -1, -1) :
		for k in range(s[i]) :
			if j - v[i][k] >= 0 :
				dp[j] = max(dp[j], dp[j - v[i][k]] + w[i][k])
			
print(dp[m])

集合的角度思考动态规划问题

在这里插入图片描述

总结

0-1背包是基础
0-1背包滚动数组优化时
必须先遍历物品再遍历背包:如果先遍历背包那么每个背包只会选择一个物品
遍历背包时,要从大往小遍历:是为了防止更新本层时使用本层已更新的数据,其实需要用的都是上面一层
完全背包则是可以用本层已更新的数据,所以优化时从前往后遍历

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值