「平均値の最適化」を二分探索に帰着させる系の典型問題!
問題概要
体のモンスターがいて、それぞれ質量は 、魔力は である。
また、 体のお助けモンスターがいて、それぞれ質量は 、魔力は である。
今、これら 体のモンスターからちょうど 5 体を選んで合体させる。ただし、お助けモンスターは最大で 1 体までしか合体に用いることはできない。合体に用いたモンスターについての (魔力の総和) を (質量の総和) で割った値の最大値を求めよ。
制約
考えたこと
魔力の重み付き平均値を最大化したいという問題だ。この手の問題は「二分探索」で解けると相場は決まっている。(魔力の総和) / (質量の総和) を 以上にできるかどうかを判定する問題を解くことにしよう。
合体に用いるモンスターの魔力を 、質量を とすると、条件は
⇔
と表せる。左辺を最大化したいということになる。これは、モンスターのうち、 が大きい順に 5 体選べばよい。ただし、お助けモンスターについては 2 体以上は選ばないこととする。
よって、判定問題が の計算量で解けた。二分探索のイテレーション回数は 100 回程度で十分であり、十分間に合う。
コード
#include <bits/stdc++.h> using namespace std; const double INF = 1LL<<30; int main() { int N, M; cin >> N >> M; vector<double> A(N), B(N), C(M), D(M); for (int i = 0; i < N; ++i) cin >> A[i] >> B[i]; for (int i = 0; i < M; ++i) cin >> C[i] >> D[i]; double low = 0, high = INF; for (int _ = 0; _ < 100; ++_) { double x = (low + high) / 2; // モンスター、お助けモンスターそれぞれについて、(魔力) - x × (質量) を大きい順に並べる vector<double> monster(N), otasuke(M); for (int i = 0; i < N; ++i) monster[i] = B[i] - x * A[i]; for (int i = 0; i < M; ++i) otasuke[i] = D[i] - x * C[i]; sort(monster.begin(), monster.end(), greater<double>()); sort(otasuke.begin(), otasuke.end(), greater<double>()); // お助けモンスターを使わない場合と、1 体使う場合 double tmp1 = monster[0] + monster[1] + monster[2] + monster[3] + monster[4]; double tmp2 = otasuke[0] + monster[0] + monster[1] + monster[2] + monster[3]; if (max(tmp1, tmp2) >= 0) low = x; else high = x; } cout << fixed << setprecision(10) << low << endl; }