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.

CF 917A - The Monster

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

  1. Initialize the answer to zero.
  2. Choose a starting position $l$.
  3. Set balance = 0.
  4. Set free = 0, representing how many question marks are currently treated as '(' and can still be flipped later.
  5. Extend the ending position $r$ from $l$ to the end of the string.
  6. If s[r] is '(', increment balance.
  7. If s[r] is ')', decrement balance.
  8. If s[r] is '?', increment both balance and free.

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
  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.
  2. Whenever the current substring length is even and balance == 0, increment the answer.
  3. 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.