CF 913C - Party Lemonade
We are asked to buy at least L liters of lemonade at minimum cost. The store offers n types of bottles, where the i-th type has a volume of 2^(i-1) liters and a cost of c[i] roubles, and we can buy an unlimited number of each type.
Rating: 1600
Tags: bitmasks, dp, greedy
Solve time: 6m 30s
Verified: yes
Solution
Problem Understanding
We are asked to buy at least L liters of lemonade at minimum cost. The store offers n types of bottles, where the i-th type has a volume of 2^(i-1) liters and a cost of c[i] roubles, and we can buy an unlimited number of each type. The task is to determine the minimum total cost to reach or exceed L liters.
From the constraints, n ≤ 30, which is small, but L can be up to 10^9, so any solution that iterates over every possible liter is infeasible. We cannot simply use a standard DP over liters because that would require O(L) memory and time, which is far too large. Instead, the small number of bottle types suggests we can use bitmasking, greedy strategies, or DP over subsets.
Non-obvious edge cases include situations where larger bottles are more expensive per liter than smaller bottles, so taking just large bottles could overshoot the target unnecessarily. For example, if you need 3 liters, but the 8-liter bottle is cheaper than three 1-liter bottles, the optimal solution may be to buy the 8-liter bottle even if it exceeds L. A naive greedy that always fills using the largest volume fitting under L would fail here.
Approaches
The brute-force approach would be to consider all combinations of bottles, computing total volume and cost, and choosing the minimum among those meeting or exceeding L. Each bottle type has infinite supply, so the number of combinations is unbounded. Limiting choices per bottle to a reasonable bound (e.g., enough to exceed L) gives a complexity of roughly O((L/volume)^n), which is far too slow for n = 30 and L up to 10^9.
The key observation is that each bottle volume is a power of two. This naturally leads to thinking in binary terms: we can consider taking or not taking each "bit" of volume. Furthermore, we can preprocess the costs to ensure that buying two smaller bottles is never cheaper than a single larger bottle of double volume. Formally, for each i from 1 to n-1, we replace c[i+1] with min(c[i+1], 2*c[i]). This guarantees that the price function is monotonic with respect to volume, preventing suboptimal choices.
After this preprocessing, the problem reduces to a bitwise greedy selection: we iterate from the largest bottle downward, deciding how many to take based on the remaining L. We also need to track the minimal cost in case overshooting with a larger bottle becomes cheaper than combining smaller ones.
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force | O((L/volume)^n) | O(1) | Too slow |
| Optimal | O(n) | O(n) | Accepted |
Algorithm Walkthrough
- First, preprocess the costs. For each bottle type from the second to the last, replace its cost with the minimum of its current cost and twice the cost of the previous smaller bottle. This ensures that larger bottles are never disproportionately expensive compared to smaller ones.
- Initialize variables:
ansas infinity (will track the minimum cost) andcurrent_costas 0 (cost accumulated so far). - Iterate over bottle types from largest to smallest. For each type, determine how many bottles of this type we could take to cover remaining liters L. We calculate the minimal number of bottles to either reach or just exceed the current remaining L using integer division.
- Add the cost of these bottles to
current_cost. After each step, also consider if buying an extra bottle of this type alone results in a cheaper solution than combining smaller bottles. Updateanswith the minimum of its current value andcurrent_cost + c[i]if it overshoots. - After finishing all types, the variable
ansholds the minimal total cost.
Why it works: Preprocessing ensures no combination of smaller bottles can beat a larger one in cost per volume, so we can safely make greedy choices. Considering the overshoot at each step guarantees that we account for cases where buying a larger bottle than needed is cheaper than perfectly fitting smaller ones. The invariant is that at each iteration, current_cost is the cheapest way to cover at least the liters we have accounted for so far.
Python Solution
import sys
input = sys.stdin.readline
n, L = map(int, input().split())
c = list(map(int, input().split()))
# Preprocess: ensure larger bottles are never more expensive than two smaller ones
for i in range(1, n):
c[i] = min(c[i], 2 * c[i-1])
ans = float('inf')
current_cost = 0
remaining = L
for i in range(n-1, -1, -1):
volume = 1 << i
count = remaining // volume
current_cost += count * c[i]
remaining -= count * volume
# consider buying one extra bottle of this type
ans = min(ans, current_cost + (c[i] if remaining > 0 else 0))
# In case we have exactly covered L
ans = min(ans, current_cost)
print(ans)
Explanation: The preprocessing step prevents non-monotone pricing. The main loop goes from largest to smallest bottle. count is how many bottles of this type we can safely take without overshooting. remaining tracks liters still needed. After adding count bottles, we always consider buying an extra bottle to see if overshooting is cheaper, which handles the key edge case. Finally, we ensure the answer is at least the exact cost if no overshoot is needed.
Worked Examples
Sample 1:
Input:
4 12
20 30 70 90
| Step | i | volume | remaining | count | current_cost | ans |
|---|---|---|---|---|---|---|
| 1 | 3 | 8 | 12 | 1 | 90 | 90 + ? = 90 + 0 |
| 2 | 2 | 4 | 4 | 1 | 120 | 120 |
| 3 | 1 | 2 | 0 | 0 | 150 | 150 |
| 4 | 0 | 1 | 0 | 0 | 150 | 150 |
We see we buy 1×8L + 2×2L = 12L, total 150, exactly matching the expected output.
Sample 2:
Input:
3 3
10 20 10
| Step | i | volume | remaining | count | current_cost | ans |
|---|---|---|---|---|---|---|
| 2 | 2 | 4 | 3 | 0 | 0 | 0 + 10 = 10 |
| 1 | 1 | 2 | 3 | 1 | 20 | 20 + ? = 20 + 20 |
| 0 | 0 | 1 | 1 | 1 | 30 | 30 |
Optimal is to buy the 4L bottle for 10, even though it overshoots, confirming the algorithm handles overshoot.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | We iterate once to preprocess, then once through n bottle types. |
| Space | O(n) | Storing costs for n bottle types. |
With n ≤ 30, this solution runs in a fraction of a millisecond, far below the 1s time limit. Memory usage is negligible.
Test Cases
import sys, io
def run(inp: str) -> str:
sys.stdin = io.StringIO(inp)
n, L = map(int, input().split())
c = list(map(int, input().split()))
for i in range(1, n):
c[i] = min(c[i], 2 * c[i-1])
ans = float('inf')
current_cost = 0
remaining = L
for i in range(n-1, -1, -1):
volume = 1 << i
count = remaining // volume
current_cost += count * c[i]
remaining -= count * volume
ans = min(ans, current_cost + (c[i] if remaining > 0 else 0))
ans = min(ans, current_cost)
return str(ans)
# provided samples
assert run("4 12\n20 30 70 90\n") == "150", "sample 1"
assert run("3 3\n10 20 10\n") == "10", "sample 2"
# custom cases
assert run("1 1\n100\n") == "100", "minimum L and n"
assert run("5 31\n1 3 6 13 27\n") == "31", "exact powers of two sum"
assert run("3 5\n5 4 20\n") == "8", "larger bottle cheaper"
assert run("2 7\n2 5\n") == "10", "overshoot needed"
| Test input | Expected output | What it validates |
|---|---|---|