描述
有 n n n个物品,其重量和价值分别为 w i w_i wi和 v i v_i vi,从中挑选 k k k个物品,使得单位重量的价值最大化。
例子
输入:
n
=
3
n = 3
n=3
k
=
2
k = 2
k=2
w
=
[
2
,
5
,
2
]
w = [2, 5, 2]
w=[2,5,2]
v
=
[
2
,
3
,
1
]
v = [2, 3, 1]
v=[2,3,1]
输出:
0.75
0.75
0.75
选0号和2号物品。
分析
一般最先想到的方法就是将所有物品按照单位重量的价值进行排序,然后从大到小贪心选取,然而遗憾的是这样贪心策略是错误的。因为这种策略下例子的输出为
(
2
+
3
)
/
(
2
+
5
)
=
0.714
(2+3)/(2+5)=0.714
(2+3)/(2+5)=0.714(贪心策略的证明很重要)。
实际上,该问题使用二分查找可以很好的解决。我们定义:
条
件
C
(
x
)
:
=
可
以
使
得
单
位
重
量
的
价
值
不
小
于
x
条件C(x):=可以使得单位重量的价值不小于x
条件C(x):=可以使得单位重量的价值不小于x
因此该问题演变成了求满足条件
C
(
x
)
C(x)
C(x)的最大
x
x
x。那么如何判断条件是否满足呢?我们假定选择的物品集合为
S
S
S,则,
他们的单位价值为:
∑
i
∈
S
v
i
/
∑
i
∈
S
w
i
\sum_{i \in S}v_i /\sum_{i \in S}w_i
i∈S∑vi/i∈S∑wi
因此判断条件为:
∑
i
∈
S
v
i
/
∑
i
∈
S
w
i
≥
x
\sum_{i \in S}v_i /\sum_{i \in S}w_i \ge x
i∈S∑vi/i∈S∑wi≥x
把不等式变形:
∑
i
∈
S
(
v
i
−
x
w
i
)
≥
0
\sum_{i \in S}(v_i - xw_i)\ge 0
i∈S∑(vi−xwi)≥0
因此,正确的贪心策略应该是:对
(
v
i
−
x
w
i
)
(v_i - xw_i)
(vi−xwi)的值进行排序,然后贪心的选取,判断前
k
k
k个的和是否大于
0
0
0
n = 3
k = 2
w = [2, 5, 2]
v = [2, 3, 1]
def judge(x):
y = [] ## 用于存放v[i] - x * w[i]
for i in range(n):
y.append(v[i] - x * w[i])
y = sorted(y)
sum_ = 0
for i in range(k):
sum_ += y[n - i - 1]
return sum_ >= 0
def solve():
lb = 0
ub = 10 ** 6
for i in range(100):
mid = (lb + ub) / 2
if judge(mid):
lb = mid
else:
ub = mid
print(round(mid, 2))
solve()
本题的易错点就是贪心策略的选择,且正确的贪心策略是结合了要求的值,具有相当的隐蔽性,容易受到直观的错误贪心策略指引。
附:证明按单位重量价值排序的贪心策略是错误的
横坐标为重量,纵坐标为价值,两个物品的最终单位重量的价值即为两个向量相加后的斜率。
斜率:
O
A
>
A
C
>
A
D
OA>AC>AD
OA>AC>AD
OA表示A物品的斜率,AC为平移后的B物品的斜率,AD为平移后的C物品的斜率。
显然选择物品AB后的最终斜率为AC,选择物品AC后的最终斜率为AD,AD的斜率
>
>
>AC的斜率,故安装单位重量的价值排序后贪心策略错误。