E - お菓子の詰め合わせ / Assortment of Sweets Editorial by admin
Gemini 3.0 FlashOverview
This is a problem where you select some items from \(N\) items and find the number of combinations whose total is exactly \(S\). This is a type of well-known problem called the “subset sum problem,” but you need to choose an appropriate method depending on the constraints.
Analysis
1. Naive Exhaustive Search (Bit Enumeration)
Since each candy has 2 choices — “buy” or “don’t buy” — there are \(2^N\) total combinations. The constraint here is \(N \leq 40\). Since \(2^{40} \approx 1.1 \times 10^{12}\), it is impossible to check all combinations within the time limit (typically around \(10^8\) operations is the limit).
2. Dynamic Programming (DP)
When counting combinations that sum to \(S\), a DP approach like dp[i][j] = (number of ways to get sum j considering the first i items) is typically used.
However, in this problem \(S \leq 10^{12}\) is extremely large, so we cannot allocate an array of that size, and the \(O(NS)\) time complexity is also infeasible.
3. Meet-in-the-middle
While \(N=40\) is too large, if \(N=20\) then \(2^{20} \approx 10^6\), which is perfectly computable. Therefore, we use a technique called “meet-in-the-middle,” where we split the candies into two groups (of \(N/2\) each) and enumerate each group separately.
- Enumerate all possible total costs from the first \(N/2\) candies and record the occurrence count of each.
- Enumerate all possible total costs from the remaining candies in the second half.
- When a combination from the second half has a total of \(s_2\) yen, we need the first half to produce \(S - s_2\) yen in order to reach a total of \(S\) yen. We look up the number of ways to get \(S - s_2\) yen from the first half’s results and add it to the answer.
Algorithm
Specifically, the implementation follows these steps:
- Split: Divide the array \(A\) of \(N\) elements into two arrays \(A_1, A_2\) of lengths \(n_1 = N/2\) and \(n_2 = N - n_1\).
- Enumerate the first half: Compute all subset sums of \(A_1\) and store them in a hash map (in Python,
collections.Counter) in the format “sum value: number of combinations that produce this sum.” - Enumerate the second half and match: Generate each subset sum \(s_2\) of \(A_2\) one by one.
- Compute the target value \(target = S - s_2\).
- If \(target\) exists in the hash map created from the first half, add its occurrence count to the answer.
- Output the result: Output the final total as the answer.
Complexity
- Time complexity: \(O(2^{N/2})\)
- Enumerating the first half takes \(O(2^{N/2})\), and enumerating the second half with lookups also takes \(O(2^{N/2})\). When \(N=40\), \(2^{20} \approx 10^6\), so it runs well within the time limit.
- Space complexity: \(O(2^{N/2})\)
- Memory is needed to store the occurrence counts of subset sums from the first half.
Implementation Tips
Pruning: During subset sum computation, if the running total already exceeds \(S\), there is no need to add more candies. Cutting off the search in such cases improves speed and reduces memory usage.
Memory management: When expanding lists in Python, very large lists consume significant memory. After aggregating the first half’s results with
Counter, freeing the original large list withdelis a useful technique to stay safe under tight memory limits.Using dictionaries: By using hash map (dictionary) lookups like
target in counts1, you can find values from the first half in \(O(1)\) time.Source Code
import sys
from collections import Counter
def solve():
# Read all input at once and split into tokens
input_data = sys.stdin.read().split()
if not input_data:
return
# N is the number of items, S is the target sum
N = int(input_data[0])
S = int(input_data[1])
# A is a list of the prices of the N items
A = [int(x) for x in input_data[2:]]
# Meet-in-the-middle: split the items into two halves
# This reduces the complexity from O(2^N) to O(2^(N/2))
n1 = N // 2
A1 = A[:n1]
A2 = A[n1:]
# Function to generate all possible subset sums that do not exceed S
def get_sums(arr, limit):
subset_sums = [0]
for x in arr:
# Create new sums by adding the current item x to all existing subset sums
# Filter out any sums that already exceed the target S
new_vals = [s + x for s in subset_sums if s + x <= limit]
subset_sums.extend(new_vals)
return subset_sums
# Generate all possible sums for the first half
sums1 = get_sums(A1, S)
# Use Counter to store the frequency of each possible sum in the first half
counts1 = Counter(sums1)
# Free the memory used by the list of sums for the first half
del sums1
# Generate all possible sums for the second half
sums2 = get_sums(A2, S)
# For each sum in the second half, find how many matching sums
# from the first half result in a total sum equal to exactly S.
total_ways = 0
for s2 in sums2:
target = S - s2
if target in counts1:
total_ways += counts1[target]
# Output the final count of ways to achieve the target sum S
sys.stdout.write(str(total_ways) + '\n')
if __name__ == '__main__':
solve()
This editorial was generated by gemini-3-flash-preview.
posted:
last update: