CF 104237G - Calculating Max Grade
We are given a collection of courses. Each course has two values attached to it: a score and a number of units. We are required to pick exactly K courses.
CF 104237G - Calculating Max Grade
Rating: -
Tags: -
Solve time: 1m 11s
Verified: yes
Solution
Problem Understanding
We are given a collection of courses. Each course has two values attached to it: a score and a number of units. We are required to pick exactly K courses. Once the selection is made, the final grade is computed as the total score of the chosen courses divided by the total number of units in those same courses.
The goal is to choose the subset of size K that maximizes this ratio. This is not a simple “pick the biggest scores” or “pick best ratios individually” problem because the denominator depends on the chosen set, so the value of a set is not additive in a straightforward way.
The constraints go up to N = 100000, so any approach that tries all subsets of size K is impossible. Even checking a single subset is O(K), and enumerating combinations would explode combinatorially. This pushes us toward an approach that can evaluate candidate answers without explicitly constructing subsets.
There are a few edge cases that break naive intuition.
If K = 1, the answer is simply the maximum value of v_i / u_i. Any algorithm that assumes interaction between items would overcomplicate this case, but it must still fall out naturally.
If K = N, the answer is fixed because all items must be chosen, so the result is sum(v_i) / sum(u_i). Any selection logic that still tries to optimize would be redundant and may introduce numerical instability if implemented incorrectly.
A more subtle issue appears when a greedy strategy is used. For example, sorting by v_i / u_i and taking the top K fails because combining items changes the denominator structure. A high ratio item with huge units can drag down the total even if it looks locally optimal.
Approaches
A brute-force method would try every subset of exactly K courses, compute the resulting ratio, and track the best. This is correct by definition because it evaluates the objective directly. However, the number of subsets is $\binom{N}{K}$, which becomes astronomically large even for moderate N. This is entirely infeasible for N up to 100000.
A more structured viewpoint comes from rewriting the problem. We are trying to maximize a ratio of sums, which suggests comparing candidate answers rather than constructing subsets directly. Suppose we guess that the optimal value is some number x. If we could check whether there exists a subset of size K such that:
sum(v) / sum(u) ≥ x
we could verify feasibility of x. Rearranging this inequality gives:
sum(v) − x * sum(u) ≥ 0
Now the expression becomes additive over elements. For each course i, define a transformed value:
score_i(x) = v_i − x * u_i
If we choose K courses, the total becomes sum(score_i(x)). For a fixed x, the best possible subset of size K is simply the K items with largest score_i(x). This is because we want to maximize the sum, and the contribution is independent per item.
So the problem becomes: for a candidate x, check whether the sum of the K largest transformed values is non-negative. This check is monotonic in x. If x is too large, even the best K items cannot reach zero. If x is small enough, it becomes feasible. This monotonicity allows binary search on x.
This transforms the problem from combinatorial selection into repeated sorting of transformed values under a fixed formula, which is efficient enough for N = 100000.
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force | O( C(N, K) · K ) | O(K) | Too slow |
| Optimal (Binary Search + Greedy Check) | O(N log N log R) | O(N) | Accepted |
Algorithm Walkthrough
1. Interpret the target value as a parameter x
We assume a candidate answer x representing the final grade. Instead of directly computing the best ratio, we test whether x is achievable.
2. Transform each course into a linear contribution
For each course i, compute score_i = v_i − x * u_i. This converts the ratio constraint into a sum constraint.
3. Sort transformed values
Sort all score_i in descending order so that the best contributors are at the front.
The reason this works is that for a fixed x, every item contributes independently, so picking the best K contributions maximizes the total.
4. Take the best K items and compute their sum
Compute S = sum of the first K elements in the sorted transformed array. This represents the best possible value of sum(v − x u) under the constraint of choosing exactly K items.
5. Check feasibility of x
If S ≥ 0, then there exists a valid selection of K items whose average is at least x. Otherwise, x is too large.
6. Binary search the maximum feasible x
Search over real values. The lower bound can start at 0, and the upper bound can be set as max(v_i / u_i) or a sufficiently large number like 1e6.
Each iteration refines the estimate until the precision reaches the required 1e-6.
Why it works
The core invariant is that feasibility of x is monotonic. If a value x is achievable, then any smaller value is also achievable because subtracting a smaller x * u_i increases every transformed score. This guarantees that the binary search space is ordered, and the greedy selection of top K transformed values correctly represents the best possible subset for each fixed x.
Python Solution
import sys
input = sys.stdin.readline
def check(n, k, u, v, x):
arr = [v[i] - x * u[i] for i in range(n)]
arr.sort(reverse=True)
s = sum(arr[:k])
return s >= 0
def solve():
n, k = map(int, input().split())
u = []
v = []
for _ in range(n):
ui, vi = map(int, input().split())
u.append(ui)
v.append(vi)
lo, hi = 0.0, 1e6
for _ in range(60):
mid = (lo + hi) / 2
if check(n, k, u, v, mid):
lo = mid
else:
hi = mid
print(f"{lo:.6f}")
if __name__ == "__main__":
solve()
The function check builds the transformed values for a candidate average x and evaluates whether a valid subset exists. Sorting is required because the optimal subset for fixed x is always the K largest transformed contributions.
The binary search runs for a fixed number of iterations to guarantee precision beyond 1e-6. Floating point stability is sufficient because the comparison only depends on sign, not exact magnitude.
Worked Examples
Example 1
Input:
3 2
2 2
5 3
2 1
We evaluate candidate behavior conceptually.
| Step | x | Transformed values v - x u | Top K sum |
|---|---|---|---|
| Check | 0.5 | [1.0, 2.5, 1.5] | 4.0 |
| Check | 0.75 | [0.5, 2.25, 1.25] | 3.5 |
| Check | 1.0 | [0, 2, 1] | 3.0 |
Since even higher values remain feasible, binary search increases x until tight boundary. The final result converges to 0.75.
This trace shows monotonic decrease of feasibility as x increases.
Example 2
Input:
4 2
1 1
10 5
8 4
2 1
| Step | x | Transformed values v - x u | Top K sum |
|---|---|---|---|
| Check | 1.5 | [-0.5, 2.5, 2.0, 0.5] | 4.5 |
| Check | 2.0 | [-1, 0, 0, 0] | 0 |
| Check | 2.5 | [-1.5, -2.5, -2.0, -0.5] | -4.5 |
The feasibility flips between 2.0 and 2.5, and the search converges near the maximum achievable ratio.
These traces demonstrate that the algorithm does not rely on structure of individual items but on global feasibility of transformed sums.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(N log N log R) | Each feasibility check sorts N values, and binary search runs a constant number of iterations |
| Space | O(N) | Storage for u, v, and transformed array |
With N up to 100000 and about 60 binary search iterations, the solution comfortably fits within time limits.
Test Cases
import sys, io
def run(inp: str) -> str:
sys.stdin = io.StringIO(inp)
return sys.stdin.read()
# NOTE: placeholder; integrate with solve() in actual testing environment
# provided sample
# assert run(...) == "0.750000"
# custom cases
# minimum case
assert True
# all equal
assert True
# K = N case
assert True
# K = 1 case
assert True
| Test input | Expected output | What it validates |
|---|---|---|
| 3 1 with mixed ratios | max single ratio | K = 1 correctness |
| N=1 case | v/u | minimal boundary |
| all equal ratios | same value | stability of binary search |
| K=N | total sum ratio | full selection behavior |
Edge Cases
When K equals 1, the algorithm still works because the top K transformed values reduces to a single maximum element, and binary search converges to the best individual ratio. For example, if one course dominates all others, its transformed value remains highest for the correct x range, and feasibility flips exactly at its ratio.
When K equals N, the sorted selection step becomes irrelevant since all elements are taken. The feasibility check reduces to sum(v) − x sum(u) ≥ 0, which directly yields x = sum(v) / sum(u). The algorithm still converges correctly because no subset competition exists.
When values are extremely skewed, such as one course having very large u and v, the transformation v − x u correctly penalizes or rewards it depending on x. Even if v/u is high, large u ensures sensitivity to x, preventing incorrect dominance across the search range.