AtCoder Beginners Contest 034 D - 食塩水

問題はこちら

問題概要

濃度{ \displaystyle p_i}と重量{ \displaystyle w_i}がわかっている { \displaystyle N}個の食塩水から{ \displaystyle K}個を選んで混ぜる時, 最大の濃度を求めよ。

考えたこと

二分探索を用いて最大濃度を計算するのはすぐわかったが, 以下のコードでのcheck関数をどう実装するか(どのようにその濃度の食塩水を作れるか否か判定するか)分からなかった。

lower = 0
upper = 100
while upper - lower > 10 ** (-5):
    half = (upper+lower) / 2
    if check(half):
        lower = half
    else:
        upper = half

print(lower)

色々実験したところ, 以下の結論に達した。そもそも, 濃度{ \displaystyle P}の食塩水が作れるとはどういうことか? 濃度が{ \displaystyle P}より大きい{ \displaystyle K}個の食塩水が集められれば, 確実に条件を達成できる。では, { \displaystyle K}個集められないときは? 濃度{ \displaystyle p_1(\leq P)}の食塩水{ \displaystyle 1}と, 濃度{ \displaystyle p_2(>P)}の食塩水{ \displaystyle 2}を混ぜるときのことを考えてみよう。それぞれの重さを{ \displaystyle w_1}, { \displaystyle w_2}として,

$$ p_1 \times w_1 + p_2 \times w_2 - P * (w_1 + w_2) = \\ (p_1 - P) \times w_1 + (p_2 - P) \times w_2 \geq 0 $$

が成立すれば, この{ \displaystyle 2}つの食塩水を混ぜることで, 濃度{ \displaystyle P}以上の食塩水を作ることができる。これを一般化することで, check関数が実装できる。 優先的に使う食塩水は, { \displaystyle (p_i - P) * w_i}について, 降順に並べることで得られる。

解答

自分が書いたコードは以下

N, K = map(int, input().split())
WP = sorted([list(map(int, input().split())) for i in range(N)], key=lambda x: x[1], reverse=True)


def check(half):
    cur = 0
    salt = sorted(WP, key=lambda x: x[0]*(x[1]-half), reverse=True)
    for i in range(K):
        cur += salt[i][0] * (salt[i][1]-half)
    if cur >= 0:
        return True
    return False


lower = 0
upper = 100
while upper - lower > 10 ** (-5):
    half = (upper+lower) / 2
    if check(half):
        lower = half
    else:
        upper = half

print(lower)