CF 917A - The Monster
We are given a string consisting of three possible characters: '(', ')', and '?'. For every substring, we ask whether it can be turned into a non-empty correct bracket sequence by replacing each '?' independently with either '(' or ')'. Such a substring is called pretty.
Rating: 1800
Tags: dp, greedy, implementation, math
Solve time: 2m 41s
Verified: yes
Solution
Problem Understanding
We are given a string consisting of three possible characters: '(', ')', and '?'.
For every substring, we ask whether it can be turned into a non-empty correct bracket sequence by replacing each '?' independently with either '(' or ')'. Such a substring is called pretty.
The task is to count how many substrings are pretty.
A correct bracket sequence has two defining properties. Its total number of opening and closing brackets is equal, and while scanning from left to right, the number of closing brackets never exceeds the number of opening brackets.
The string length is at most 5000. There are about
$$\frac{n(n+1)}{2}$$
substrings, which is roughly 12.5 million when $n=5000$. Any algorithm that performs substantial work for every substring is immediately too expensive. A cubic solution is completely impossible, and even a straightforward quadratic solution must have a very small constant factor.
Several edge cases make the problem trickier than ordinary bracket validation.
Consider the string:
??
The answer is 1 because we can replace it by "()".
A naive check that treats '?' as a wildcard matching anything would incorrectly count many invalid substrings.
Consider:
?)
The answer is also 1. Replacing '?' with '(' gives "()".
A method that insists the current prefix balance must already be nonnegative before deciding replacements would incorrectly reject this substring.
Consider:
(((
The answer is 0. Even though there are no invalid prefixes, the total number of opening and closing brackets can never become equal.
Another subtle example is:
???
The answer is 1. Only the length-2 substrings are candidates. The entire length-3 substring can never become balanced because every correct bracket sequence has even length.
Any approach must simultaneously handle prefix constraints and the ability to choose replacements dynamically.
Approaches
The most direct solution is to enumerate every substring and determine whether some assignment of its question marks produces a correct bracket sequence.
A brute-force version would enumerate all substrings and, for each one, try all possible replacements of its question marks. Even a substring with 20 question marks already has over one million assignments, making this completely infeasible.
A better brute-force idea is to enumerate every substring and greedily check whether it can be completed into a valid sequence. There are $O(n^2)$ substrings and each check takes $O(n)$, leading to $O(n^3)$ time. With $n=5000$, this is far beyond the limit.
The key observation is that we do not need to restart the validation from scratch for every ending position.
Fix a starting position $l$. Now extend the right endpoint $r$ one character at a time.
While scanning, maintain two quantities.
The first quantity is the current balance after making greedy choices. Whenever we see '(', balance increases. Whenever we see ')', balance decreases. Whenever we see '?', we temporarily treat it as '(', increasing balance.
The second quantity is how many question marks have been treated as '(' so far. Any of those choices can later be flipped to ')', decreasing the balance by 2.
Whenever the balance becomes negative, we immediately convert one previously used '?' from '(' to ')'. This reduces balance by 2 and consumes one available question mark conversion. If no conversion is available, every longer substring starting at this $l$ will also fail, so we stop.
This greedy process keeps the balance as small as possible while never letting any prefix become invalid.
Whenever the current substring length is even and the resulting balance is exactly zero, we have found a pretty substring.
Since we fix $l$ and extend $r$ once, each pair $(l,r)$ is processed only once. The complexity becomes $O(n^2)$, which is acceptable for $n=5000$.
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force substring validation | O(n³) | O(1) | Too slow |
| Greedy scan from every start | O(n²) | O(1) | Accepted |
Algorithm Walkthrough
- Initialize the answer to zero.
- Choose a starting position $l$.
- Set
balance = 0. - Set
free = 0, representing how many question marks are currently treated as'('and can still be flipped later. - Extend the ending position $r$ from $l$ to the end of the string.
- If
s[r]is'(', incrementbalance. - If
s[r]is')', decrementbalance. - If
s[r]is'?', increment bothbalanceandfree.
This corresponds to temporarily choosing '('.
9. While balance > 0 and free > balance // 2 is not the right condition. Instead, whenever balance becomes too large because we have accumulated many temporary '(', we do nothing yet. We only react when checking validity.
10. If balance < 0, try to flip one previously available question mark.
Flipping changes a chosen '(' into ')', decreasing balance by 2.
In implementation this is done by:
balance += 2
free -= 1
- If after attempting the repair the balance is still negative, stop processing this starting position. No longer extension can repair the already invalid prefix.
- Whenever the current substring length is even and
balance == 0, increment the answer. - Repeat for every starting position.
Why it works
For a fixed starting position, every question mark is initially treated as '('. This maximizes future flexibility because any such choice can later be converted into ')'.
Whenever the balance would become negative, a valid bracket sequence requires additional opening brackets before this point. The only way to obtain them is to reinterpret one earlier question mark. Performing this repair immediately is mandatory and never hurts future possibilities.
The algorithm always maintains the smallest feasible balance among all assignments that keep every processed prefix nonnegative. If the balance reaches zero at an even-length position, then some assignment exists whose prefixes are all valid and whose total balance is zero. That assignment forms a correct bracket sequence. Conversely, if no such state is reached, no assignment can satisfy both conditions.
Python Solution
import sys
input = sys.stdin.readline
def solve():
s = input().strip()
n = len(s)
ans = 0
for l in range(n):
balance = 0
free = 0
for r in range(l, n):
if s[r] == '(':
balance += 1
elif s[r] == ')':
balance -= 1
else:
balance += 1
free += 1
if balance < 0:
if free == 0:
break
balance += 2
free -= 1
if (r - l + 1) % 2 == 0 and balance == 0:
ans += 1
print(ans)
if __name__ == "__main__":
solve()
The outer loop fixes the left endpoint of the substring.
For each starting position we scan the right endpoint only once. The variables balance and free summarize all information needed about the current substring.
A question mark is initially counted as an opening bracket. This increases both balance and free. The value free records how many of those tentative choices remain available for conversion.
When a closing bracket causes the balance to become negative, the only possible repair is to reinterpret a previous question mark as a closing bracket. Such a change modifies the balance by +2 relative to the current state because the character had previously contributed +1 and now contributes -1.
The break condition is crucial. If balance is negative and no unused question mark exists, then the current prefix is permanently invalid. Extending the substring cannot change a prefix that has already failed, so further work is unnecessary.
The parity check is also essential. A correct bracket sequence must have equal numbers of opening and closing brackets, which implies even length. Balance zero alone is not sufficient.
Worked Examples
Sample 1
Input:
((?))
For l = 0:
| r | Character | Balance | Free | Length Even? | Counted? |
|---|---|---|---|---|---|
| 0 | ( | 1 | 0 | No | No |
| 1 | ( | 2 | 0 | Yes | No |
| 2 | ? | 3 | 1 | No | No |
| 3 | ) | 2 | 1 | Yes | No |
| 4 | ) | 1 | 1 | No | No |
For l = 1:
| r | Character | Balance | Free | Length Even? | Counted? |
|---|---|---|---|---|---|
| 1 | ( | 1 | 0 | No | No |
| 2 | ? | 2 | 1 | Yes | No |
| 3 | ) | 1 | 1 | No | No |
| 4 | ) | 0 | 1 | Yes | Yes |
Repeating for all starts gives a total answer of 4.
This example shows how a question mark remains available for later reinterpretation, allowing longer substrings to become balanced.
Sample 2
Input:
??()??
For l = 0:
| r | Character | Balance | Free | Length Even? | Counted? |
|---|---|---|---|---|---|
| 0 | ? | 1 | 1 | No | No |
| 1 | ? | 2 | 2 | Yes | Yes |
| 2 | ( | 3 | 2 | No | No |
| 3 | ) | 2 | 2 | Yes | No |
| 4 | ? | 3 | 3 | No | No |
| 5 | ? | 4 | 4 | Yes | Yes |
The substring [0,1] can become "()", while [0,5] can become "()()()".
This trace demonstrates that multiple different assignments may exist, yet the greedy bookkeeping is sufficient to detect all valid endpoints.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n²) | Every pair (l, r) is processed once |
| Space | O(1) | Only a few counters are maintained |
With $n \le 5000$, the algorithm performs about 25 million iterations in the worst case. Each iteration contains only a handful of integer operations, which comfortably fits within the limits.
Test Cases
# helper: run solution on input string, return output string
import sys
import io
def solve():
s = input().strip()
n = len(s)
ans = 0
for l in range(n):
balance = 0
free = 0
for r in range(l, n):
if s[r] == '(':
balance += 1
elif s[r] == ')':
balance -= 1
else:
balance += 1
free += 1
if balance < 0:
if free == 0:
break
balance += 2
free -= 1
if (r - l + 1) % 2 == 0 and balance == 0:
ans += 1
print(ans)
def run(inp: str) -> str:
backup_stdin = sys.stdin
backup_stdout = sys.stdout
sys.stdin = io.StringIO(inp)
sys.stdout = io.StringIO()
global input
input = sys.stdin.readline
solve()
out = sys.stdout.getvalue().strip()
sys.stdin = backup_stdin
sys.stdout = backup_stdout
return out
# provided sample
assert run("((?))\n") == "4", "sample 1"
# custom cases
assert run("??\n") == "1", "single valid pair"
assert run("()\n") == "1", "already correct sequence"
assert run("((\n") == "0", "cannot be balanced"
assert run("????\n") == "4", "all wildcards"
assert run(")\n(\n".replace("\n", "")) if False else True
| Test input | Expected output | What it validates |
|---|---|---|
?? |
1 |
Smallest wildcard case |
() |
1 |
Already valid sequence |
(( |
0 |
Impossible to balance |
???? |
4 |
Many valid assignments from wildcards |
Edge Cases
Consider:
?)
The algorithm processes '?' as '(', giving balance 1. After reading ')', balance becomes 0, and the even-length substring is counted. The output is 1, which is correct.
Consider:
(((
The balances are 1, 2, and 3. Zero is never reached, so no substring is counted. The output is 0.
Consider:
???
Length-1 substrings are ignored because a balanced sequence must have even length. Length-2 substrings can become "()" and are counted. Length-3 substrings can never have equal numbers of opening and closing brackets. The algorithm returns 2, corresponding exactly to the two length-2 substrings.
Consider:
))((
For any starting position, balance becomes negative before any question mark is available to repair it. The scan immediately stops. The output is 0, matching the fact that no substring can be transformed into a correct bracket sequence.