CF 180C - Letter
We are given a string consisting of uppercase and lowercase letters. Patrick wants to transform it into a "fancy" string, defined as having all uppercase letters on the left and all lowercase letters on the right. We can change the case of any letter at the cost of one action.
Rating: 1400
Tags: dp
Solve time: 1m 12s
Verified: yes
Solution
Problem Understanding
We are given a string consisting of uppercase and lowercase letters. Patrick wants to transform it into a "fancy" string, defined as having all uppercase letters on the left and all lowercase letters on the right. We can change the case of any letter at the cost of one action. Our goal is to compute the minimum number of such actions needed to achieve the fancy format.
The input string can be as long as 100,000 characters. This immediately rules out solutions that examine every possible partition and compute the number of changes from scratch for each one in linear time, because that would be O(n²) and too slow. We need a solution that scans the string at most a constant number of times, ideally O(n). The string is guaranteed to be non-empty, so we do not need to handle an empty input case, but we do need to handle strings that are already all uppercase, all lowercase, or alternating letters.
A non-obvious edge case arises when the string is already fancy. For example, the input ABCdef requires 0 changes, and a naive approach that always counts both sides could mistakenly output a positive number. Another tricky scenario is aAaA, where uppercase and lowercase letters are fully interleaved. The algorithm must correctly compute the optimal split point, which might be in the middle of the string, and changing letters to match that split point.
Approaches
The brute-force approach iterates over every possible split of the string, from 0 to n, treating the left side as all uppercase and the right side as all lowercase. For each split, we count the number of lowercase letters in the left segment and the number of uppercase letters in the right segment. Summing these gives the number of actions needed for that split. We then take the minimum across all splits. While this is conceptually straightforward, the cost is O(n²) in the worst case because for each of n splits, counting letters on both sides requires O(n). This is far too slow for n up to 100,000.
The key insight for optimization is that the problem can be reduced to a prefix/suffix counting strategy. We can precompute for each index the cumulative number of lowercase letters up to that position and the cumulative number of uppercase letters from that position to the end. Then for any split point, we can compute the number of required changes in O(1) using these cumulative counts. This reduces the total complexity to O(n) time and O(n) space. Conceptually, we are scanning once from left to right to build the prefix counts and once from right to left for suffix counts, and then evaluating all split points in a single linear pass.
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force | O(n²) | O(1) | Too slow |
| Prefix/Suffix Counts | O(n) | O(n) | Accepted |
Algorithm Walkthrough
- Compute a prefix array
lower_prefixwherelower_prefix[i]counts the number of lowercase letters ins[0..i-1]. This represents the number of changes needed to convert the left segment to all uppercase if we split afteri-1. - Compute a suffix array
upper_suffixwhereupper_suffix[i]counts the number of uppercase letters ins[i..n-1]. This represents the number of changes needed to convert the right segment to all lowercase if we split beforei. - Initialize a variable
min_actionswith a large number. Iterate over all split pointsifrom 0 to n. For each split, the number of actions required islower_prefix[i] + upper_suffix[i]. Updatemin_actionsto the minimum over all splits. - Output
min_actions.
Why it works: At every split point, the prefix array accurately tells us how many lowercase letters would need to be converted to uppercase on the left, and the suffix array tells us how many uppercase letters would need to be converted to lowercase on the right. Evaluating all splits ensures we find the globally optimal point because any fancy string can be represented as some split between uppercase and lowercase letters.
Python Solution
import sys
input = sys.stdin.readline
s = input().strip()
n = len(s)
lower_prefix = [0] * (n + 1)
for i in range(n):
lower_prefix[i + 1] = lower_prefix[i] + (1 if s[i].islower() else 0)
upper_suffix = [0] * (n + 1)
for i in range(n - 1, -1, -1):
upper_suffix[i] = upper_suffix[i + 1] + (1 if s[i].isupper() else 0)
min_actions = n # maximum possible actions is n
for i in range(n + 1):
actions = lower_prefix[i] + upper_suffix[i]
if actions < min_actions:
min_actions = actions
print(min_actions)
The prefix and suffix arrays store cumulative counts to avoid repeated scanning of the string. The loop that computes min_actions evaluates all possible split points, including before the first character and after the last, which correctly handles strings that are already all uppercase or all lowercase.
Worked Examples
For the input PRuvetSTAaYA:
| i | s[i] | lower_prefix[i+1] | upper_suffix[i] | Actions = lower_prefix + upper_suffix |
|---|---|---|---|---|
| 0 | P | 0 | 7 | 0 + 7 = 7 |
| 1 | R | 0 | 7 | 0 + 7 = 7 |
| 2 | u | 1 | 7 | 1 + 7 = 8 |
| 3 | v | 2 | 6 | 2 + 6 = 8 |
| 4 | e | 3 | 6 | 3 + 6 = 9 |
| 5 | t | 4 | 6 | 4 + 6 = 10 |
| 6 | S | 4 | 5 | 4 + 5 = 9 |
| 7 | T | 4 | 4 | 4 + 4 = 8 |
| 8 | A | 4 | 3 | 4 + 3 = 7 |
| 9 | a | 5 | 3 | 5 + 3 = 8 |
| 10 | Y | 5 | 2 | 5 + 2 = 7 |
| 11 | A | 5 | 1 | 5 + 1 = 6 |
| 12 | 5 | 0 | 5 + 0 = 5 |
The minimum actions is 5, which matches the expected output.
For an already fancy string ABCdef:
| i | lower_prefix[i+1] | upper_suffix[i] | Actions |
|---|---|---|---|
| 0 | 0 | 3 | 3 |
| 1 | 0 | 3 | 3 |
| 2 | 0 | 3 | 3 |
| 3 | 0 | 3 | 3 |
| 4 | 0 | 2 | 2 |
| 5 | 0 | 1 | 1 |
| 6 | 0 | 0 | 0 |
The minimum is 0, correctly identifying no changes are needed.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Two passes to build prefix and suffix arrays, one pass to compute minimum actions |
| Space | O(n) | Two arrays of size n+1 to store cumulative counts |
Given n ≤ 10^5, the O(n) solution easily executes within 1 second. Memory usage is well below 256 MB.
Test Cases
import sys, io
def run(inp: str) -> str:
sys.stdin = io.StringIO(inp)
s = input().strip()
n = len(s)
lower_prefix = [0] * (n + 1)
for i in range(n):
lower_prefix[i + 1] = lower_prefix[i] + (1 if s[i].islower() else 0)
upper_suffix = [0] * (n + 1)
for i in range(