Prefix Sum Queries
Problem Overview
| Attribute | Value |
|---|---|
| CSES Link | Prefix Sum Queries |
| Difficulty | Hard |
| Category | Range Queries / Segment Tree |
| Time Limit | 1 second |
| Key Technique | Segment Tree with Custom Merge Function |
Learning Goals
After solving this problem, you will be able to:
- Design segment trees that store multiple values per node
- Implement custom merge functions for non-trivial range queries
- Combine prefix sums with maximum operations in a single structure
- Handle point updates while maintaining prefix sum properties
Problem Statement
Problem: Given an array of n integers, process two types of queries:
- Update (type 1): Change the value at position k to u
- Max prefix sum (type 2): Find the maximum prefix sum in range [a, b]
A prefix sum at position i within range [a, b] is: sum(arr[a], arr[a+1], …, arr[i])
Input:
- Line 1: n (array size) and q (number of queries)
- Line 2: n integers (the initial array)
- Next q lines: Either “1 k u” (update) or “2 a b” (query)
Output: For each type 2 query, output the maximum prefix sum
Constraints:
- 1 <= n, q <= 2 x 10^5
- -10^9 <= arr[i], u <= 10^9
Example
Input:
8 4
3 -1 4 1 -5 9 2 6
2 1 8
2 2 3
1 3 -5
2 1 8
Output:
13
3
6
Explanation:
- Query
2 1 8: Prefix sums are [3, 2, 6, 7, 2, 11, 13, 19]. Max = 13 at position 7. - Query
2 2 3: Range [-1, 4], prefix sums [-1, 3]. Max = 3. - Update
1 3 -5: arr[3] becomes -5. - Query
2 1 8: New prefix sums [3, 2, -3, -2, -7, 2, 4, 10]. Max = 10.
Intuition: How to Think About This Problem
The Challenge
Key Question: Why can’t we use a simple segment tree with maximum values?
A prefix sum depends on all elements before it within the query range. We cannot simply store one value per segment.
The Key Insight
When merging two segments [L, mid] and [mid+1, R]:
- Max prefix could be entirely in left segment
- OR it could extend through left into right segment
Left: sum=5, maxPrefix=7 Right: sum=3, maxPrefix=4
Merged maxPrefix = max(7, 5+4) = 9
^ ^
left only | left.sum + right.maxPrefix
Solution: Store TWO values per node: (total_sum, max_prefix_sum)
Solution 1: Brute Force
For each query, iterate through the range computing prefix sums.
def solve_brute_force(arr, queries):
results = []
for query in queries:
if query[0] == 1: # Update
arr[query[1] - 1] = query[2]
else: # Query
a, b = query[1], query[2]
max_prefix, curr = float('-inf'), 0
for i in range(a - 1, b):
curr += arr[i]
max_prefix = max(max_prefix, curr)
results.append(max_prefix)
return results
Complexity: O(q * n) time, O(1) space - Too slow for constraints.
Solution 2: Segment Tree with Custom Merge
Node Structure
| Field | Meaning |
|---|---|
total |
Sum of all elements in this segment |
max_prefix |
Maximum prefix sum from segment’s left boundary |
Merge Function
def merge(left, right):
total = left.total + right.total
max_prefix = max(left.max_prefix, left.total + right.max_prefix)
return (total, max_prefix)
Dry Run Example
Array [3, -1, 4, -2]:
Tree Structure:
[1-4]
(sum=4, max=6)
/ \
[1-2] [3-4]
(sum=2, max=3) (sum=2, max=4)
/ \ / \
[1] [2] [3] [4]
(3, 3) (-1,-1) (4, 4) (-2,-2)
Query [2,4]:
Combine [2] with [3-4]: merge((-1,-1), (2,4))
total = -1 + 2 = 1
max_prefix = max(-1, -1 + 4) = 3
Verify: arr[2..4] = [-1, 4, -2]
Prefixes: -1, 3, 1 -> Max = 3 [Correct]
Code (Python)
import sys
def solve():
data = sys.stdin.read().split()
idx = 0
n, q = int(data[idx]), int(data[idx+1]); idx += 2
arr = [0] + [int(data[idx+i]) for i in range(n)]; idx += n
tree = [(0, float('-inf'))] * (4 * n)
def merge(l, r):
return (l[0] + r[0], max(l[1], l[0] + r[1]))
def build(node, start, end):
if start == end:
tree[node] = (arr[start], arr[start])
return
mid = (start + end) // 2
build(2*node, start, mid)
build(2*node+1, mid+1, end)
tree[node] = merge(tree[2*node], tree[2*node+1])
def update(node, start, end, pos, val):
if start == end:
tree[node] = (val, val)
return
mid = (start + end) // 2
if pos <= mid:
update(2*node, start, mid, pos, val)
else:
update(2*node+1, mid+1, end, pos, val)
tree[node] = merge(tree[2*node], tree[2*node+1])
def query(node, start, end, l, r):
if r < start or end < l:
return (0, float('-inf'))
if l <= start and end <= r:
return tree[node]
mid = (start + end) // 2
return merge(query(2*node, start, mid, l, r),
query(2*node+1, mid+1, end, l, r))
build(1, 1, n)
results = []
for _ in range(q):
t = int(data[idx]); idx += 1
if t == 1:
k, u = int(data[idx]), int(data[idx+1]); idx += 2
update(1, 1, n, k, u)
else:
a, b = int(data[idx]), int(data[idx+1]); idx += 2
results.append(query(1, 1, n, a, b)[1])
print('\n'.join(map(str, results)))
if __name__ == "__main__":
solve()
Complexity
| Metric | Value | Explanation |
|---|---|---|
| Time | O((n + q) log n) | Build O(n), each operation O(log n) |
| Space | O(n) | Segment tree with 4n nodes |
Common Mistakes
Mistake 1: Storing Only Max Element
# WRONG
def merge(left, right):
return max(left, right) # Finds max element, not max prefix sum
Fix: Store (total, max_prefix) tuple.
Mistake 2: Wrong Merge Order
# WRONG - order matters!
max_prefix = max(right[1], right[0] + left[1]) # Reversed!
Fix: Use max(left.max_prefix, left.total + right.max_prefix).
Mistake 3: Wrong Identity Element
# WRONG
return (0, 0) # Empty segment max_prefix should be -infinity
Fix: Use (0, float('-inf')) for out-of-range.
Mistake 4: Integer Overflow (C++)
Values up to 10^9, sums can reach 2 x 10^14. Use long long.
Edge Cases
| Case | Input | Output | Why |
|---|---|---|---|
| Single element | query [1,1] |
arr[1] | Only one prefix |
| All negative | [-5,-3,-1] |
-1 | Shortest prefix best |
| All positive | [1,2,3] |
6 | Full range best |
| Large values | 10^9 | Use long long | Overflow prevention |
When to Use This Pattern
Use when:
- Need max/min prefix sums over arbitrary ranges
- Point updates required
- O(log n) query complexity needed
Don’t use when:
- Static queries only (sparse table simpler)
- Need max subarray sum (different segment tree structure)
- Range updates (need lazy propagation)
Related Problems
Easier (Do First)
| Problem | Why It Helps |
|---|---|
| Static Range Sum Queries | Basic prefix sums |
| Dynamic Range Sum Queries | Segment tree basics |
Similar Difficulty
| Problem | Key Difference |
|---|---|
| Range Update Queries | Lazy propagation |
| Maximum Subarray | Kadane’s algorithm |
Harder (Do After)
| Problem | New Concept |
|---|---|
| Polynomial Queries | Complex lazy propagation |
| Range Updates and Sums | Multiple update types |
Key Takeaways
- Core Idea: Store (total_sum, max_prefix_sum) per node for correct merging
- Time: O(n) brute force per query -> O(log n) with segment tree
- Space: O(4n) enables O(log n) operations
- Pattern: When simple aggregation fails, ask “what extra info enables correct merging?”
Practice Checklist
- Explain why both sum AND max_prefix are needed
- Derive the merge function from first principles
- Handle identity element correctly
- Extend to similar problems (max suffix sum)