CF 2024F - Many Games
Each game has a success probability $pi/100$ and a reward $wi$. We may choose any subset of games. The catch is that we receive the reward only if every chosen game is won. If even one chosen game is lost, the final reward becomes zero.
Rating: 2900
Tags: dp, math
Solve time: 5m 49s
Verified: no
Solution
Problem Understanding
Each game has a success probability $p_i/100$ and a reward $w_i$.
We may choose any subset of games. The catch is that we receive the reward only if every chosen game is won. If even one chosen game is lost, the final reward becomes zero.
For a chosen subset $S$,
$$\text{expected value} = \left(\prod_{i\in S}\frac{p_i}{100}\right) \left(\sum_{i\in S}w_i\right).$$
We must maximize this quantity.
The first thing that stands out is the huge value of $n$, up to $2\cdot 10^5$. Any algorithm that explicitly considers subsets is impossible. Even $O(n^2)$ is far too large.
The second unusual constraint is
$$p_i \cdot w_i \le 2\cdot 10^5.$$
At first glance it looks unrelated to the objective, but it is actually the key that makes the problem solvable.
There are only 100 possible probability values. That strongly suggests grouping games by probability.
A subtle edge case is $p_i=100$. Such a game never decreases the probability product, because multiplying by $1$ changes nothing. Since rewards are positive, every $100%$ game should always be taken.
For example:
2
100 5
100 7
The optimal answer is $12$. Ignoring probability-100 games would immediately lose correctness.
Another easy mistake is assuming that if two games have the same probability, we may choose arbitrary ones among them.
Consider:
3
90 100
90 50
90 1
If we decide to take exactly two games with probability $90%$, taking rewards $100$ and $50$ is always at least as good as taking $100$ and $1$. Among equal probabilities, larger rewards dominate smaller rewards.
A third trap is believing that the total reward of an optimal solution can be arbitrarily large because $n$ is huge. The special constraint prevents that. Proving this bound is the central observation of the solution.
Approaches
The brute force solution is obvious. Enumerate every subset, compute its reward sum and probability product, then keep the best answer.
This is correct because it checks every possibility.
Unfortunately it requires $2^n$ subsets. Even for $n=50$ this is hopeless, while the actual limit is $2\cdot 10^5$.
The next question is what property of the optimal subset can be exploited.
Suppose the optimal subset has total reward $W$.
Take any chosen game $i$. Removing it cannot improve the answer. Let
$$q_i=\frac{p_i}{100}.$$
Optimality gives
$$q_i W \ge W-w_i.$$
Rearranging,
$$w_i \ge (1-q_i)W.$$
Multiplying by $p_i$,
$$p_iw_i \ge \frac{p_i(100-p_i)}{100}W.$$
For every $1\le p_i\le 99$,
$$\frac{p_i(100-p_i)}{100}\ge 0.99.$$
Since $p_iw_i\le 2\cdot10^5$,
$$W \le \frac{2\cdot10^5}{0.99} < 202021.$$
This is the breakthrough.
The optimal solution never needs total reward above roughly $2\cdot10^5$.
Now we can think of reward sum as a knapsack dimension.
There is one more observation.
For a fixed probability $p$, every chosen game contributes the same multiplicative factor $p/100$. If we decide to take exactly $k$ games from that probability group, the best choice is simply the $k$ largest rewards.
Thus each probability group becomes a sequence of prefix sums after sorting rewards in descending order.
A second counting argument shows that for probability $p<100$, an optimal solution can take at most
$$\left\lceil \frac1{\ln(100/p)} \right\rceil$$
games from that group. Summing these limits over all $p=1,\dots,99$ gives only about 500 candidate games. The original $2\cdot10^5$ games collapse to a few hundred relevant ones.
After that reduction, a standard knapsack over total reward becomes feasible.
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force | $O(2^n)$ | $O(1)$ | Too slow |
| Optimal | $O(W \cdot K)$ | $O(W)$ | Accepted |
Here $W\approx 3\cdot10^5$ and $K\approx 500$.
Algorithm Walkthrough
1. Separate all games with probability 100
Every such game should always be chosen.
Let
$$\text{base}=\sum w_i \quad (p_i=100).$$
These games do not affect the probability product.
2. Group the remaining games by probability
For every $p\in[1,99]$, collect all rewards having that probability.
Sort each group in descending order.
3. Keep only the relevant largest rewards
For probability $p$, compute
$$\text{limit} = \left\lceil \frac1{\ln(100/p)} \right\rceil.$$
Only the largest limit rewards from that group can ever matter.
Any additional game is provably useless in an optimal solution.
4. Run a knapsack on reward sums
Let
$$dp[s]$$
be the maximum probability product achievable with total additional reward exactly $s$.
Initialize
$$dp[0]=1.$$
For every retained game $(p,w)$,
$$dp[s] = \max(dp[s],, dp[s-w]\cdot p/100).$$
This is a standard 0/1 knapsack update.
5. Compute the final answer
For every reward sum $s$,
$$\text{candidate} = dp[s]\cdot (base+s).$$
Take the maximum over all $s$.
Why it works
The crucial invariant is that after processing some set of retained games, dp[s] stores the largest possible probability product among all subsets whose total reward is exactly s.
The transition is the standard 0/1 knapsack transition. Either the current game is not chosen, or it is chosen and multiplies the probability product by $p/100$.
The pruning step is safe because for a fixed probability group, only the largest rewards can ever be part of an optimal solution. Furthermore, the analytical bound on the number of selected games from one probability group guarantees that every optimal solution survives the pruning.
Since every optimal subset corresponds to some reachable reward sum $s$, and dp[s] stores the best probability product for that sum, maximizing
$$dps$$
examines exactly the objective value of the best subset for every possible reward sum. The maximum among them is the global optimum.
Python Solution
import sys
import math
input = sys.stdin.readline
n = int(input())
groups = [[] for _ in range(101)]
base = 0
for _ in range(n):
p, w = map(int, input().split())
if p == 100:
base += w
else:
groups[p].append(w)
items = []
for p in range(1, 100):
if not groups[p]:
continue
groups[p].sort(reverse=True)
limit = math.ceil(1.0 / math.log(100.0 / p))
if limit < len(groups[p]):
groups[p] = groups[p][:limit]
for w in groups[p]:
items.append((w, p / 100.0))
MAXS = base
for w, _ in items:
MAXS += w
dp = [0.0] * (MAXS + 1)
dp[0] = 1.0
for w, prob in items:
for s in range(MAXS, w - 1, -1):
cand = dp[s - w] * prob
if cand > dp[s]:
dp[s] = cand
ans = float(base)
for s in range(MAXS + 1):
if dp[s] > 0:
ans = max(ans, dp[s] * (base + s))
print(f"{ans:.10f}")
The first section separates probability-100 games because they are always beneficial.
The grouping phase sorts rewards inside each probability bucket. The pruning limit comes from the analytical bound on how many games of the same probability can appear in an optimal solution.
After pruning, the number of remaining games is only a few hundred. That is the reason the knapsack becomes feasible.
The DP stores probability products directly as floating point values. The products can become very small, but the pruning bound keeps the number of multiplicative factors limited, so ordinary double precision is sufficient.
The reverse iteration order in the knapsack loop is essential. Using forward iteration would accidentally allow the same game to be selected multiple times.
Worked Examples
Sample 1
Input:
3
80 80
70 100
50 200
Relevant DP states:
| Chosen games | Reward sum | Probability product | Expected value |
|---|---|---|---|
| {80} | 80 | 0.8 | 64 |
| {70} | 100 | 0.7 | 70 |
| {50} | 200 | 0.5 | 100 |
| {80,70} | 180 | 0.56 | 100.8 |
| {80,50} | 280 | 0.40 | 112 |
| {70,50} | 300 | 0.35 | 105 |
| {80,70,50} | 380 | 0.28 | 106.4 |
The maximum is $112$, achieved by choosing the first and third games.
Sample 2
Input:
2
100 1
100 1
After preprocessing:
| Variable | Value |
|---|---|
| base | 2 |
| retained items | none |
The DP contains only dp[0]=1.
Final value:
$$1\cdot(2+0)=2.$$
Answer: $2$.
This example demonstrates why probability-100 games must be handled separately.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | $O(W\cdot K)$ | Knapsack over reward sums with about 500 retained games |
| Space | $O(W)$ | One DP array |
The reward-sum dimension is bounded by the structural proof derived from $p_iw_i\le2\cdot10^5$. After pruning, only a few hundred games remain, so the DP easily fits within the limits.
Test Cases
# helper: run solution on input string, return output string
import sys
import io
from math import isclose
def run(inp: str) -> str:
# paste solution into solve()
pass
# sample 1
# expected 112.0
# sample 2
# expected 2.0
# custom minimum case
# 1 game
# 50% chance, reward 10
# answer = 5
# custom all probability 100
# answer equals sum of rewards
# custom equal probabilities
# checks that largest rewards are preferred
# custom boundary style
# p*w = 200000
# ensures constraint edge is handled
| Test input | Expected output | What it validates |
|---|---|---|
| One game only | Single-game EV | Minimum size |
| All $p=100$ | Sum of rewards | Mandatory inclusion of certainty games |
| Many equal probabilities | Uses largest rewards | Group pruning correctness |
| $p\cdot w=200000$ | Valid answer | Boundary constraint |
Edge Cases
Consider:
2
100 5
100 7
The algorithm places both rewards into base = 12.
No DP item is created.
The final answer becomes
$$1\cdot12=12.$$
This correctly handles certainty games.
Now consider:
3
90 100
90 50
90 1
After sorting the probability-90 group, the rewards become:
100, 50, 1
Any solution using two games from this group is represented by the two largest rewards. The pruning argument guarantees that smaller rewards never replace larger ones within the same probability class.
Finally consider:
1
1 200000
The expected value is
$$0.01 \cdot 200000 = 2000.$$
The algorithm keeps the game, updates dp[200000] = 0.01, and computes exactly this value. The boundary $p_iw_i=200000$ causes no special difficulty.