文章目录
- 求解最佳方案数
- 例题
- 思路
- 代码
- 混合背包问题
- 例题
- 思路
- 代码
- 有依赖的背包问题
- 例题
- 思路
- 代码
- 考察思维的一些背包题目
- 机器分配
- 金明的预算方案
- 货币系统
- 能量石
- 总结
求解最佳方案数
例题
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出 最优选法的方案数。注意答案可能很大,请输出答案模 109+7 的结果。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示 方案数 模 109+7 的结果。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 6
输出样例:
2
思路
代码
N = 1010
MOD = int(1e9) + 7
f, g = [0] * N, [1] * N
for i in range(n) :
v, w = map(int, input().split())
for j in range(m, v - 1, -1) :
maxv = max(f[j], f[j - v] + w)
s = 0
if maxv == f[j] : s = g[j]
if maxv == f[j - v] + w : s = (s + g[j - v]) % MOD
g[j] = s
f[j] = maxv
print(g[m])
混合背包问题
例题
有 N 种物品和一个容量是 V 的背包。
物品一共有三类:
第一类物品只能用1次(01背包);
第二类物品可以用无限次(完全背包);
第三类物品最多只能用 si 次(多重背包);
每种体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
si=−1 表示第 i 种物品只能用1次;
si=0 表示第 i 种物品可以用无限次;
si>0 表示第 i 种物品可以使用 si 次;
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
−1≤si≤1000
输入样例
4 5
1 2 -1
2 4 1
3 4 0
4 5 2
输出样例:
8
思路
代码
N = 1010
f = [0] * N
n, m = map(int, input().split())
for i in range(n) :
v, w, s = map(int, input().split())
if s == 0 :
for j in range(v, m + 1) :
f[j] = max(f[j], f[j - v] + w)
else :
if s == -1 :
s = 1
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 :
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 的背包。
物品之间具有依赖关系,且依赖关系组成一棵树的形状。如果选择一个物品,则必须选择它的父节点。
如下图所示:
如果选择物品5,则必须选择物品1和2。这是因为2是5的父节点,1是2的父节点。
每件物品的编号是 i,体积是 vi,价值是 wi,依赖的父节点编号是 pi。物品的下标范围是 1…N。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品个数和背包容量。
接下来有 N 行数据,每行数据表示一个物品。
第 i 行有三个整数 vi,wi,pi,用空格隔开,分别表示物品的体积、价值和依赖的物品编号。
如果 pi=−1,表示根节点。 数据保证所有物品构成一棵树。
输出格式
输出一个整数,表示最大价值。
数据范围
1≤N,V≤100
1≤vi,wi≤100
父节点编号范围:
内部结点:1≤pi≤N;
根节点 pi=−1;
输入样例
5 7
2 3 -1
2 2 1
3 5 1
4 7 2
3 6 2
输出样例:
11
思路
代码
N = 110
f = [[0] * N for _ in range(N)]
h = [-1] * N
e = [0] * N
ne = [-1] * N
v, w = [0] * N, [0] * N
idx = 0
def add(a, b) :
global idx
e[idx] = b
ne[idx] = h[a]
h[a] = idx
idx += 1
def dfs(u) :
for i in range(v[u], m + 1) : f[u][i] = w[u]
i = h[u]
while i != -1 :
son = e[i]
dfs(son)
for j in range(m, v[u] - 1, -1) :
for k in range(j - v[u] + 1) :
f[u][j] = max(f[u][j], f[u][j - k] + f[son][k])
i = ne[i]
n, m = map(int, input().split())
root = 0
for i in range(1, n + 1) :
v[i], w[i], p = map(int, input().split())
if p == -1 :
root = i
else :
add(p, i)
dfs(root)
print(f[root][m])
考察思维的一些背包题目
机器分配
总公司拥有 M 台 相同 的高效设备,准备分给下属的 N 个分公司。
各分公司若获得这些设备,可以为国家提供一定的盈利。盈利与分配的设备数量有关。
问:如何分配这M台设备才能使国家得到的盈利最大?
求出最大盈利值。
分配原则:每个公司有权获得任意数目的设备,但总台数不超过设备数 M。
输入格式
第一行有两个数,第一个数是分公司数 N,第二个数是设备台数 M;
接下来是一个 N×M 的矩阵,矩阵中的第 i 行第 j 列的整数表示第 i 个公司分配 j 台机器时的盈利。
输出格式
第一行输出最大盈利值;
接下 N 行,每行有 2 个数,即分公司编号和该分公司获得设备台数。
答案不唯一,输出任意合法方案即可。
数据范围
1≤N≤10,
1≤M≤15
输入样例:
3 3
30 40 50
20 30 50
20 25 30
输出样例:
70
1 1
2 1
3 1
- 思路
- 代码
N = 10
f = [[0] * N for _ in range(N)]
a = [[0] * N for _ in range(N)]
ways = [0] * N
n, m = map(int, input().split())
for i in range(1, n + 1) :
a[i][1 : m + 1] = list(map(int, input().split()))
for i in range(1, n + 1) :
for j in range(m + 1) :
for k in range(j + 1) :
f[i][j] = (f[i][j], f[i - 1][j - k] + a[i][k])
print(f[n][m])
j = m
for i in range(n, 0, -1) :
for k in range(j + 1) :
if f[i][j] == f[i - 1][j - k] + a[i][k] :
ways[i] = k
j -= k
break
for i in range(1, n + 1) :
print(i, ways[i])
金明的预算方案
金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间。
更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N元钱就行”。
今天一早,金明就开始做预算了,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:
如果要买归类为附件的物品,必须先买该附件所属的主件。
每个主件可以有0个、1个或2个附件。
附件不再有从属于自己的附件。
金明想买的东西很多,肯定会超过妈妈限定的N元。
于是,他把每件物品规定了一个重要度,分为5等:用整数1~5表示,第5等最重要。
他还从因特网上查到了每件物品的价格(都是10元的整数倍)。
他希望在不超过N元(可以等于N元)的前提下,使每件物品的价格与重要度的乘积的总和最大。
设第j件物品的价格为v[j],重要度为w[j],共选中了k件物品,编号依次为j1,j2,…,jk,则所求的总和为:
v[j1]∗w[j1]+v[j2]∗w[j2]+…+v[jk]∗w[jk](其中*为乘号)
请你帮助金明设计一个满足要求的购物单。
输入格式
输入文件的第1行,为两个正整数,用一个空格隔开:N m,其中N表示总钱数,m为希望购买物品的个数。
从第2行到第m+1行,第j行给出了编号为j-1的物品的基本数据,每行有3个非负整数v p q,其中v表示该物品的价格,p表示该物品的重要度(1~5),q表示该物品是主件还是附件。
如果q=0,表示该物品为主件,如果q>0,表示该物品为附件,q是所属主件的编号。
输出格式
输出文件只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(<200000)。
数据范围
N<32000,m<60,v<10000
输入样例:
1000 5
800 2 0
400 5 1
300 5 1
400 3 0
500 2 0
输出样例:
2200
- 思路
- 代码
N = 32010
M = 70
f = [0] * N
master = [[0, 0] for _ in range(M)]
serven = [[] for _ in range(M)]
m, n = map(int, input().split())
for i in range(1, n + 1) :
v, p, q = map(int, input().split())
if not q :
master[i] = [v, v * p]
else :
serven[q].append([v, v * p])
for i in range(1, n + 1) :
if master[i][0] :
for j in range(m, -1, -1) :
for k in range(1 << len(serven[i])) :
v, w = master[i][0], master[i][1]
for u in range(len(serven[i])) :
if k >> u :
v += serven[i][u][0]
w += serven[i][u][1]
if j >= v :
f[j] = max(f[j], f[j - v] + w)
print(f[m])
货币系统
在网友的国度中共有 n 种不同面额的货币,第 i 种货币的面额为 a[i],你可以假设每一种货币都有无穷多张。
为了方便,我们把货币种数为 n、面额数组为 a[1…n] 的货币系统记作 (n,a)。
在一个完善的货币系统中,每一个非负整数的金额 x 都应该可以被表示出,即对每一个非负整数 x,都存在 n 个非负整数 t[i] 满足 a[i]×t[i] 的和为 x。
然而,在网友的国度中,货币系统可能是不完善的,即可能存在金额 x 不能被该货币系统表示出。
例如在货币系统 n=3, a=[2,5,9] 中,金额 1,3 就无法被表示出来。
两个货币系统 (n,a) 和 (m,b) 是等价的,当且仅当对于任意非负整数 x,它要么均可以被两个货币系统表出,要么不能被其中任何一个表出。
现在网友们打算简化一下货币系统。
他们希望找到一个货币系统 (m,b),满足 (m,b) 与原来的货币系统 (n,a) 等价,且 m 尽可能的小。
他们希望你来协助完成这个艰巨的任务:找到最小的 m。
输入格式
输入文件的第一行包含一个整数 T,表示数据的组数。
接下来按照如下格式分别给出 T 组数据。
每组数据的第一行包含一个正整数 n。
接下来一行包含 n 个由空格隔开的正整数 a[i]。
输出格式
输出文件共有 T 行,对于每组数据,输出一行一个正整数,表示所有与 (n,a) 等价的货币系统 (m,b) 中,最小的 m。
数据范围
1≤n≤100,
1≤a[i]≤25000,
1≤T≤20
输入样例:
2
4
3 19 10 6
5
11 29 13 19 17
输出样例:
2
5
-
思路
一些性质:- a1, a2,…,an一定都可以被表示出来
- 在最优解中,b1,b2,…, bn一定是从a1,a2, …,an中选择
- b1,b2,…,bn一定不能被其他bi表示
-
代码
N = 25010
T = int(input())
for t in range(T) :
f = [0] * N
n = int(input())
nums = list(map(int, input().split()))
nums.sort()
m = nums[n - 1]
res = 0
f[0] = 1
for i in range(1, n + 1) :
if not f[nums[i - 1]] : res += 1
for j in range(nums[i - 1] -1, m + 1) :
f[j] += f[j - nums[i - 1]]
print(res)
能量石
岩石怪物杜达生活在魔法森林中,他在午餐时收集了 N 块能量石准备开吃。
由于他的嘴很小,所以一次只能吃一块能量石。
能量石很硬,吃完需要花不少时间。
吃完第 i 块能量石需要花费的时间为 Si 秒。
杜达靠吃能量石来获取能量。
不同的能量石包含的能量可能不同。
此外,能量石会随着时间流逝逐渐失去能量。
第 i 块能量石最初包含 Ei 单位的能量,并且每秒将失去 Li 单位的能量。
当杜达开始吃一块能量石时,他就会立即获得该能量石所含的全部能量(无论实际吃完该石头需要多少时间)。
能量石中包含的能量最多降低至 0。
请问杜达通过吃能量石可以获得的最大能量是多少?
输入格式
第一行包含整数 T,表示共有 T 组测试数据。
每组数据第一行包含整数 N,表示能量石的数量。
接下来 N 行,每行包含三个整数 Si,Ei,Li。
输出格式
每组数据输出一个结果,每个结果占一行。
结果表示为 Case #x: y,其中 x 是组别编号(从 1 开始),y 是可以获得的最大能量值。
数据范围
1≤T≤10,
1≤N≤100,
1≤Si≤100,
1≤Ei≤105,
0≤Li≤105
输入样例:
3
4
20 10 1
5 30 5
100 30 1
5 80 60
3
10 4 1000
10 3 1000
10 8 1000
2
12 300 50
5 200 0
输出样例:
Case #1: 105
Case #2: 8
Case #3: 500
样例解释
在样例#1中,有 N=4 个宝石。杜达可以选择的一个吃石头顺序是:
吃第四块石头。这需要 5 秒,并给他 80 单位的能量。
吃第二块石头。这需要 5 秒,并给他 5 单位的能量(第二块石头开始时具有 30 单位能量,5 秒后失去了 25 单位的能量)。
吃第三块石头。这需要 100 秒,并给他 20 单位的能量(第三块石头开始时具有 30 单位能量,10 秒后失去了 10 单位的能量)。
吃第一块石头。这需要 20 秒,并给他 0 单位的能量(第一块石头以 10 单位能量开始,110 秒后已经失去了所有的能量)。
他一共获得了 105 单位的能量,这是能获得的最大值,所以答案是 105。
在样本案例#2中,有 N=3 个宝石。
无论杜达选择吃哪块石头,剩下的两个石头的能量都会耗光。
所以他应该吃第三块石头,给他提供 8 单位的能量。
在样本案例#3中,有 N=2 个宝石。杜达可以:
吃第一块石头。这需要 12 秒,并给他 300 单位的能量。
吃第二块石头。这需要 5 秒,并给他 200 单位的能量(第二块石头随着时间的推移不会失去任何能量!)。
所以答案是 500。
- 思路
最优解一定是按照一定顺序来吃:假定为a1, a2,…an的顺序。
a i 和 a i + 1 是 其 中 相 邻 的 两 个 能 量 石 a_i和a_{i+1}是其中相邻的两个能量石 ai和ai+1是其中相邻的两个能量石,按照这个顺序吃,假设开始吃的时间为t,则获得的能量为 S 1 = e i − t ∗ l i + e i + 1 − ( t + s i ) ∗ l i + 1 S_1 = e_i-t*l_i + e_{i + 1} - (t + s_i) * l_{i + 1} S1=ei−t∗li+ei+1−(t+si)∗li+1,如果交换顺序则 S 2 = e i + 1 − t ∗ l i + 1 + e i − ( t + s i + 1 ) ∗ l i S_2 = e_{i + 1}-t*l_{i+1} + e_{i} - (t + s_{i+ 1}) * l_{i} S2=ei+1−t∗li+1+ei−(t+si+1)∗li。如果按规定顺序,那么 S 1 > = S 2 S_1 >= S_2 S1>=S2,化简即 s i / l i < = s i + 1 / l i + 1 s_i / l_i <= s_{i + 1} / l_{i + 1} si/li<=si+1/li+1。则每个物品按照这样的顺序选取才能得到最大,下面就是0-1背包的板子。
本题值得注意的是,本题状态表示是恰好时间j时能量最大值,因为能量会因为时间损耗。 - 代码
T = int(input())
for t in range(T) :
n = int(input())
stone = []
m = 0
for i in range(n) :
s, e, l = map(int, input().split())
stone.append([s, e, l])
m += s
f = [-100010] * (m + 1)
f[0] = 0
stone.sort(key = lambda x : x[0] / max(x[2], 1e-8))
for i in range(n) :
s, e, l = stone[i]
for j in range(m, s - 1, -1) :
f[j] = max(f[j], f[j - s] + e - (j - s) * l)
res = 0
for i in range(m + 1) :
res = max(res, f[i])
print(f'Case #{t + 1}: {res}')
- 总结
求最优解,与推进顺序相关,既有增量又有损耗的题目,可以假定一个顺序,然后假设顺序对调比较大小,得到一定单调性。方便后序加以选择
总结
背包问题就此告一段落,三篇博客已经涵盖了背包的大部分题型,奥力给!!!