CF 103965A - Mood Balance
We are given a sequence of integers. Think of them as daily “changes” to some running value that starts at zero. As we go from left to right, we maintain a running sum. Each element either increases or decreases this running sum.
Rating: -
Tags: -
Solve time: 37s
Verified: yes
Solution
Problem Understanding
We are given a sequence of integers. Think of them as daily “changes” to some running value that starts at zero. As we go from left to right, we maintain a running sum. Each element either increases or decreases this running sum.
The task is to determine the highest value this running sum ever reaches while processing the array from left to right.
So if the array is interpreted as increments and decrements of a score, we are asked: what is the maximum score achieved at any prefix boundary.
The output is a single integer: the maximum prefix sum over the whole array.
The constraints in this problem are not large enough to require anything beyond a single linear scan. That already rules out any solution that tries to recompute prefix sums repeatedly or uses nested loops, because those would turn into quadratic behavior and are unnecessary.
A subtle edge case appears when all values are negative. In that case, the running sum starts at zero and never improves, so the answer should remain zero. Any solution that assumes at least one positive prefix increase will fail here. For example, input [-3, -2, -5] must produce 0, not -3.
Another edge case is when the maximum prefix sum occurs at the first element. For example [5, -10, 1] should return 5. A naive implementation that only checks after full accumulation or forgets to consider intermediate prefixes can miss this.
Approaches
A brute-force approach would compute the sum of every prefix separately and take the maximum. For each index i, we sum from 0 to i. This works correctly because it directly follows the definition of prefix sums. However, recomputing the sum for each prefix leads to a total of roughly n + (n-1) + ... + 1 operations, which is O(n²). With typical constraints up to 10^5, this becomes far too slow.
The key observation is that prefix sums are incremental. Instead of recomputing from scratch, we can maintain a running sum while scanning the array once. Each new element updates the current sum, and we immediately compare it with the best value seen so far. This reduces the problem from repeated recomputation to a single pass accumulation.
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force prefix recomputation | O(n²) | O(1) | Too slow |
| Single pass running sum | O(n) | O(1) | Accepted |
Algorithm Walkthrough
- Start with two variables:
current_sum = 0andbest = 0. The initial best is zero because we are allowed to consider the starting state before any element is applied. - Iterate through the array from left to right. At each position, add the current element to
current_sum. This simulates applying the next “mood change” or “increment”. - After updating
current_sum, compare it withbest. Ifcurrent_sumis larger, replacebestwith it. This step ensures we always remember the highest prefix value encountered so far. - Continue until the end of the array. The final value of
bestis the answer.
Why it works
The running sum at position i is exactly the prefix sum of the first i elements. Every possible prefix sum is evaluated exactly once during the scan. Since the maximum of a set is preserved under incremental comparison, tracking the maximum seen so far guarantees that no candidate prefix is missed. There is no dependence between non-adjacent prefixes beyond the accumulated sum, so no additional structure is required.
Python Solution
import sys
input = sys.stdin.readline
n = int(input())
a = list(map(int, input().split()))
current_sum = 0
best = 0
for x in a:
current_sum += x
if current_sum > best:
best = current_sum
print(best)
The implementation is a direct translation of the scan described above. The only subtle decision is initializing best to zero rather than negative infinity. This reflects that the running sum starts before any elements are taken, so the empty prefix is valid.
Worked Examples
Example 1
Input:
[1, -2, 3]
| Step | Element | Current Sum | Best |
|---|---|---|---|
| 0 | start | 0 | 0 |
| 1 | 1 | 1 | 1 |
| 2 | -2 | -1 | 1 |
| 3 | 3 | 2 | 2 |
This demonstrates how the maximum can occur after a later positive rebound, not necessarily at the beginning.
Example 2
Input:
[-1, -2, -3]
| Step | Element | Current Sum | Best |
|---|---|---|---|
| 0 | start | 0 | 0 |
| 1 | -1 | -1 | 0 |
| 2 | -2 | -3 | 0 |
| 3 | -3 | -6 | 0 |
This confirms that the algorithm correctly preserves zero when all prefixes decrease.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each element is processed once with constant-time updates |
| Space | O(1) | Only two running variables are maintained |
The linear scan is optimal because every element must be read at least once, and no additional structure is required.
Test Cases
import sys, io
def run(inp: str) -> str:
sys.stdin = io.StringIO(inp)
n = int(input())
a = list(map(int, input().split()))
current_sum = 0
best = 0
for x in a:
current_sum += x
best = max(best, current_sum)
return str(best)
# provided samples (illustrative)
assert run("3\n1 -2 3\n") == "2"
# all negative
assert run("3\n-1 -2 -3\n") == "0"
# all positive
assert run("4\n1 2 3 4\n") == "10"
# alternating
assert run("5\n1 -1 1 -1 1\n") == "1"
# single element
assert run("1\n5\n") == "5"
| Test input | Expected output | What it validates |
|---|---|---|
| all negative | 0 | empty-prefix handling |
| all positive | sum | full accumulation correctness |
| alternating | 1 | correct intermediate max |
| single element | value itself | boundary condition |
Edge Cases
For an input consisting entirely of negative numbers, the running sum continuously decreases. Since the algorithm always compares against zero initialized best, it correctly returns zero. For example [-4, -2, -7] produces 0 because no prefix exceeds the empty prefix.
For a single-element array, the first update immediately sets both current and best to that value if it is positive, or keeps best at zero if negative. This matches the idea that we always compare against the starting state before processing any elements.