CF 1201C - Maximum Median
We are given a list of numbers and allowed to repeatedly increase any single element by one. Each such increment has a cost of one operation, and we have a total budget of at most $k$ operations.
Rating: 1400
Tags: binary search, greedy, math, sortings
Solve time: 3m 59s
Verified: yes
Solution
Problem Understanding
We are given a list of numbers and allowed to repeatedly increase any single element by one. Each such increment has a cost of one operation, and we have a total budget of at most $k$ operations. The goal is not to maximize the sum or any global statistic, but specifically to maximize the median after the array is sorted.
Since the array size is odd, the median is the element that ends up in position $n/2$ after sorting. The key difficulty is that when we increase some elements, the sorting order may change, so the median is not tied to a fixed index in the original array.
The constraints are large enough that any strategy that simulates operations one by one is impossible. With $n$ up to $2 \cdot 10^5$ and $k$ up to $10^9$, even a single operation loop of length $k$ would clearly exceed time limits. Any viable solution must reduce the problem to either sorting plus linear processing or a logarithmic search over the answer.
A subtle failure mode appears when thinking greedily about increasing the median position directly. If we only increase the current median element, we ignore the fact that other elements to its right may also need to be raised to preserve its median status after sorting. For example, if we only increment the median in isolation, a smaller element to its right could still block it from being the median after sorting.
A second mistake arises if we try to maintain a dynamic sorted structure and repeatedly adjust the median. This becomes too slow due to repeated insertions and deletions, and also obscures the real structure of the problem: only elements at or above the median position matter for the final answer.
Approaches
A brute-force approach would simulate the process of applying operations in all possible distributions among elements. After each increment, we would resort the array and track the median. Even if we restrict ourselves to only distributing increments among promising elements, the number of possible distributions grows combinatorially with $k$, making this approach infeasible.
The key observation is that once the array is sorted, the median sits at a fixed position, and only the elements from that position onward can influence how large it can become. Any element to the left of the median does not constrain the median value because increasing them only helps the median or leaves it unchanged after re-sorting.
This leads to a greedy structure: we want to raise the median and everything to its right so that the median can be pushed upward as far as possible. If we fix a target value for the median, we can check how many operations are needed to make all elements in the right half at least that value. This naturally leads to a feasibility check.
From here, binary search becomes the natural tool. We search for the maximum median value such that it is possible to raise the median and the suffix of the array to that level within budget $k$. Each check is linear after sorting.
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force | Exponential in $k$ | $O(n)$ | Too slow |
| Optimal (binary search + greedy check) | $O(n \log (k + \max a))$ | $O(n)$ | Accepted |
Algorithm Walkthrough
We first sort the array so that the median position becomes fixed. Let $m = n/2$. From this point, the median we control is at index $m$, and only indices $m, m+1, \ldots, n-1$ matter for enforcing a candidate median value.
- We sort the array because any reasoning about the median requires a stable ordering. Without sorting, we cannot identify which elements constrain the median.
- We define a function that checks whether a given value $x$ can be achieved as the median. The idea is to force every element from index $m$ onward to be at least $x$, because if all of them are at least $x$, the median is also at least $x$.
- To compute the cost of achieving this, we iterate from index $m$ to $n-1$ and accumulate the deficit $\max(0, x - a[i])$. This represents the minimum number of increments needed to lift these elements to $x$. The reasoning is that increasing elements left of the median does not help guarantee the median reaches $x$, because they can be pushed out of the median region by larger elements.
- If the total required cost is within $k$, then the value $x$ is feasible. Otherwise, it is too large.
- We binary search over $x$. The lower bound is the current median value, and the upper bound can be extended by adding all $k$ operations to the median element in the best case.
- The final answer is the largest feasible $x$.
Why it works
The crucial invariant is that in a sorted array, the median is determined entirely by the suffix starting at index $m$. Any strategy that successfully makes all elements in this suffix at least $x$ guarantees the median is also at least $x$, because at least half of the elements will be $\ge x$. Conversely, if the suffix cannot be raised to $x$ within the budget, then any attempt to achieve median $x$ fails since some element in the controlling region would remain below $x$, forcing the median down. This makes the feasibility check both necessary and sufficient.
Python Solution
import sys
input = sys.stdin.readline
def can(a, m, k, x):
cost = 0
for i in range(m, len(a)):
if a[i] < x:
cost += x - a[i]
if cost > k:
return False
return True
def solve():
n, k = map(int, input().split())
a = list(map(int, input().split()))
a.sort()
m = n // 2
lo = a[m]
hi = a[m] + k
ans = lo
while lo <= hi:
mid = (lo + hi) // 2
if can(a, m, k, mid):
ans = mid
lo = mid + 1
else:
hi = mid - 1
print(ans)
if __name__ == "__main__":
solve()
The sorting step is essential because it locks the median position so that all reasoning about feasibility becomes positional rather than value-based. The helper function can directly encodes the idea that only the right half contributes to enforcing a candidate median value.
Binary search is safe here because feasibility is monotonic: if a value $x$ is achievable, then any smaller value is also achievable since it requires no more operations.
The upper bound choice $a[m] + k$ reflects the best possible scenario where all operations are concentrated on the median element itself.
Worked Examples
Example 1
Input:
3 2
1 3 5
Sorted array is [1, 3, 5], median index is 1.
We test feasibility:
| x | cost from suffix [3,5] | feasible |
|---|---|---|
| 3 | 0 | yes |
| 4 | 1 (only 5 already ok, 3 needs +1) | yes |
| 5 | 2 | yes |
| 6 | 3 | no |
The binary search stops at 5.
This shows that both median and right-side elements must be lifted together; increasing only the median is not enough because the suffix constrains feasibility.
Example 2
Input:
5 3
1 2 10 10 10
Sorted array is [1, 2, 10, 10, 10], median index is 2.
| x | cost for [10,10,10] | cost total | feasible |
|---|---|---|---|
| 10 | 0 | 0 | yes |
| 11 | 3 | 3 | yes |
| 12 | 6 | 6 | no |
Answer is 11.
This demonstrates that once the suffix already contains large values, only partial lifting is needed, and the budget directly controls how far the median can be pushed upward.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | $O(n \log (k + \max a))$ | Sorting takes $O(n \log n)$, each feasibility check is $O(n)$, and binary search runs in logarithmic range over possible median values |
| Space | $O(1)$ extra | Only the array storage is used aside from input |
The constraints allow up to $2 \cdot 10^5$ elements, and the logarithmic factor is at most around 30, making this comfortably fast within limits.
Test Cases
import sys, io
def run(inp: str) -> str:
sys.stdin = io.StringIO(inp)
import sys
input = sys.stdin.readline
n, k = map(int, input().split())
a = list(map(int, input().split()))
a.sort()
m = n // 2
def can(x):
cost = 0
for i in range(m, n):
if a[i] < x:
cost += x - a[i]
if cost > k:
return False
return True
lo, hi = a[m], a[m] + k
ans = lo
while lo <= hi:
mid = (lo + hi) // 2
if can(mid):
ans = mid
lo = mid + 1
else:
hi = mid - 1
return str(ans)
# samples
assert run("3 2\n1 3 5\n") == "5"
# custom: minimum
assert run("1 10\n5\n") == "15"
# all equal
assert run("5 3\n7 7 7 7 7\n") == "8"
# large skew
assert run("5 5\n1 1 1 1 10\n") == "3"
# no budget
assert run("3 0\n1 2 3\n") == "2"
| Test input | Expected output | What it validates |
|---|---|---|
| single element | direct increase | base case behavior |
| all equal | uniform lifting | symmetry handling |
| skewed array | suffix-driven cost | correctness of greedy cost |
| zero budget | no change | boundary constraint |
Edge Cases
A key edge case is when the median is already the maximum element in the array. In that case, the suffix is very small and increasing only the median position might seem sufficient, but the feasibility check correctly accounts for any elements to the right that still need lifting.
Another case occurs when $k$ is extremely large. The binary search will push the median upward even beyond all existing elements, and the cost is entirely determined by how many elements lie in the suffix, not by their initial distribution.