CF 2193D - Monster Game
We are given a sequence of swords, each having a strength value, and a sequence of monsters, each requiring a certain number of strikes to be defeated. Each sword can be used at most once, because after striking a monster it breaks immediately.
Rating: 1100
Tags: binary search, sortings, two pointers
Solve time: 1m 41s
Verified: yes
Solution
Problem Understanding
We are given a sequence of swords, each having a strength value, and a sequence of monsters, each requiring a certain number of strikes to be defeated. Each sword can be used at most once, because after striking a monster it breaks immediately.
Before starting the game, we choose a threshold value $x$. Only swords with strength at least $x$ are usable; all weaker swords are ignored completely. With the usable swords, we proceed level by level, and at level $i$ we must spend $b_i$ distinct swords to defeat that monster. Once we run out of usable swords, we stop at the current level.
The score is computed as $x \times (\text{number of fully completed levels})$. The goal is to choose $x$ to maximize this score.
The key structure is that increasing $x$ reduces the number of available swords but increases the multiplier. The trade-off is monotone in a way that suggests sorting and prefix reasoning.
The constraints allow up to $2 \cdot 10^5$ total $n$, so any solution with $O(n^2)$ per test is immediately impossible. Even $O(n \log n)$ must be used carefully, and any per-value simulation over all distinct sword strengths must be reduced to linear or near-linear amortized behavior.
A naive attempt might try every possible $x$ from the set of all $a_i$, simulate the game each time, and compute how many levels can be completed. That would require up to $O(n)$ simulation per candidate and $O(n)$ candidates, leading to $O(n^2)$ overall, which is far too slow.
A more subtle failure case comes from greedy simulation without sorting swords. For example, if stronger swords are not prioritized, a naive allocation might waste them early and underestimate achievable levels for a given threshold.
Another edge case appears when all swords are identical. Then the answer is purely determined by prefix sums of $b_i$, and any incorrect handling of “strictly less than $x$” vs “at least $x$” leads to off-by-one errors in available counts.
Approaches
The brute-force idea is straightforward: pick a candidate difficulty $x$, filter all swords with strength at least $x$, and then simulate level-by-level consuming swords. Each level consumes $b_i$ swords, so we decrement a counter until we cannot proceed further. This is correct because it directly mirrors the rules of the game.
The issue is cost. If we try all possible values of $x$, there are up to $n$ distinct sword strengths. For each, filtering costs $O(n)$, and simulating levels costs another $O(n)$. This leads to $O(n^2)$ per test case in the worst case.
The key observation is that for a fixed $x$, only the count of usable swords matters, not their identities. Once we sort swords in descending order of strength, increasing $x$ corresponds to gradually activating prefixes of this sorted array. Instead of recomputing from scratch, we can sweep thresholds from high to low and maintain how many swords are available.
The second important idea is how to compute the number of levels we can finish given a fixed number of available swords. Since levels are sequential and each consumes $b_i$ resources, we precompute prefix sums of $b$. The number of levels completed is the largest index $k$ such that $\sum_{i=1}^k b_i \leq \text{available swords}$.
This reduces each evaluation to a binary search on prefix sums, or even better, we can precompute the maximum reachable level for each possible sword count once and reuse it conceptually while sweeping strengths.
Finally, we evaluate the score for each threshold as:
$$\text{score} = (\text{current threshold}) \times (\text{max levels achievable with available swords})$$
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force | $O(n^2)$ | $O(n)$ | Too slow |
| Optimal | $O(n \log n)$ | $O(n)$ | Accepted |
Algorithm Walkthrough
- Sort the sword strengths in descending order. This lets us interpret choosing a threshold $x$ as selecting a prefix of this sorted list. The prefix length directly gives the number of usable swords.
- Compute prefix sums of the monster requirements $b_i$. This allows us to quickly determine how many levels can be completed with a given number of swords.
- For each possible prefix length $k$ (meaning we use the top $k$ strongest swords), determine the best threshold value associated with it. The natural choice is the smallest sword in that prefix, since that is the maximum $x$ that still includes all $k$ swords.
- For each $k$, compute how many levels can be completed. This is the largest index $i$ such that prefix_sum[i] ≤ k. This can be found via binary search.
- Compute candidate score as $x \cdot \text{levels}$, and track the maximum over all $k$.
The reason this works is that optimal thresholds only change when we include or exclude a sword. Between two consecutive distinct sword strengths, the set of usable swords does not change, so checking intermediate values of $x$ is unnecessary.
Why it works
For any fixed threshold $x$, the set of usable swords is exactly those with $a_i \ge x$. Among all thresholds that produce the same usable set, increasing $x$ improves the score without changing feasibility. Therefore the optimal choice of $x$ is always equal to some sword strength. This collapses the search space from continuous values to at most $n$ candidates, and each candidate is fully characterized by a prefix of the sorted strengths.
Python Solution
import sys
input = sys.stdin.readline
from bisect import bisect_right
def solve():
t = int(input())
for _ in range(t):
n = int(input())
a = list(map(int, input().split()))
b = list(map(int, input().split()))
a.sort(reverse=True)
pref = [0] * (n + 1)
for i in range(n):
pref[i + 1] = pref[i] + b[i]
# precompute max level for k swords via binary search on prefix sums
best = 0
for k in range(1, n + 1):
x = a[k - 1]
# max levels with k swords
levels = bisect_right(pref, k) - 1
best = max(best, x * levels)
print(best)
if __name__ == "__main__":
solve()
The solution first sorts sword strengths so that every prefix corresponds to a valid choice of usable swords under some threshold. The prefix sums over $b_i$ allow fast conversion from “number of swords available” to “how many levels can be cleared”.
The binary search step is subtle: bisect_right(pref, k) - 1 returns the largest number of levels whose total requirement does not exceed $k$. This avoids simulating level-by-level consumption.
A common mistake is reversing the meaning of prefix indices: the prefix sum array includes a zero at the start, so level counts must be offset carefully. Another mistake is forgetting that each test case needs independent prefix recomputation.
Worked Examples
Example 1
Input:
n = 3
a = [1, 3, 4]
b = [2, 1, 1]
Sorted swords:
a = [4, 3, 1]
prefix b = [0, 2, 3, 4]
| k | x (threshold) | swords | levels (≤k) | score |
|---|---|---|---|---|
| 1 | 4 | 1 | 0 | 0 |
| 2 | 3 | 2 | 1 | 3 |
| 3 | 1 | 3 | 2 | 2 |
Maximum is 3.
This shows that more swords do not necessarily improve the score, since lowering the threshold reduces the multiplier.
Example 2
Input:
n = 4
a = [4, 4, 1, 4]
b = [2, 2, 4, 1]
Sorted swords:
a = [4, 4, 4, 1]
prefix b = [0, 2, 4, 8, 9]
| k | x | swords | levels | score |
|---|---|---|---|---|
| 1 | 4 | 1 | 0 | 0 |
| 2 | 4 | 2 | 1 | 4 |
| 3 | 4 | 3 | 1 | 4 |
| 4 | 1 | 4 | 3 | 3 |
Maximum is 4.
This example highlights that increasing sword count is only useful if it increases reachable levels.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | $O(n \log n)$ | Sorting dominates; each test does a linear scan with binary search |
| Space | $O(n)$ | Prefix arrays and sorted list |
The constraints allow total $2 \cdot 10^5$ elements, so this approach comfortably fits within limits.
Test Cases
import sys, io
def run(inp: str) -> str:
sys.stdin = io.StringIO(inp)
import bisect
def solve():
t = int(input())
for _ in range(t):
n = int(input())
a = list(map(int, input().split()))
b = list(map(int, input().split()))
a.sort(reverse=True)
pref = [0]
for x in b:
pref.append(pref[-1] + x)
best = 0
for k in range(1, n + 1):
x = a[k - 1]
levels = bisect.bisect_right(pref, k) - 1
best = max(best, x * levels)
print(best)
old = sys.stdin
out = io.StringIO()
sys.stdout = out
solve()
sys.stdout = sys.__stdout__
sys.stdin = old
return out.getvalue().strip()
# provided samples
assert run("""5
3
1 3 4
2 1 1
2
2 3
1 1
4
1 2 3 4
2 2 1 1
6
4 4 1 4 5 4
2 2 4 1 2 2
10
1000000000 1000000000 1000000000 1000000000 1000000000 1000000000 1000000000 1000000000 1000000000 1000000000
1 1 1 1 1 1 1 1 1 1
""") == """3
4
3
8
10000000000"""
# custom tests
assert run("""1
1
5
1
""") == "0"
assert run("""1
5
5 4 3 2 1
1 1 1 1 1
""") == "10"
assert run("""1
4
10 1 10 1
3 2 2 1
""") == "20"
assert run("""1
6
6 5 4 3 2 1
2 2 2 2 2 2
""") == "18"
| Test input | Expected output | What it validates |
|---|---|---|
| single sword, cannot proceed | 0 | no levels possible edge case |
| many weak levels | 10 | greedy accumulation correctness |
| mixed strengths | 20 | threshold interaction correctness |
| uniform requirements | 18 | prefix boundary correctness |
Edge Cases
A minimal case with one sword shows that if the only sword has strength below any useful threshold, the answer collapses to zero because no level can be completed. The algorithm handles this because for $k=1$, the computed levels from prefix sums is zero, so the score remains zero regardless of $x$.
A case where all $b_i = 1$ stresses the prefix logic. Every additional sword directly translates to one more level, so the best score tends to come from balancing large $x$ with sufficient prefix length. The binary search on prefix sums correctly captures this linear accumulation without simulation.
A mixed-strength case tests whether sorting aligns threshold interpretation correctly. Since thresholds are tied to sorted prefixes, any mismatch between original and sorted order would produce incorrect level counts. The algorithm avoids this by separating the two domains entirely: $a$ is sorted, $b$ remains in original level order, and only prefix sums of $b$ are used.