CF 1610G - AmShZ Wins a Bet

We are given a string of parentheses S that has been modified by repeatedly performing a wrapping operation. Each operation allows you to take any contiguous substring B and surround it with a pair of parentheses, inserting it back into its original place in the string.

CF 1610G - AmShZ Wins a Bet

Rating: 3300
Tags: data structures, greedy, hashing
Solve time: 1m 54s
Verified: no

Solution

Problem Understanding

We are given a string of parentheses S that has been modified by repeatedly performing a wrapping operation. Each operation allows you to take any contiguous substring B and surround it with a pair of parentheses, inserting it back into its original place in the string. The resulting string after all operations is provided, and the goal is to reconstruct the original string, choosing the lexicographically smallest possibility if there are multiple valid answers.

The input guarantees that the first character is ), which is significant because it rules out some naive assumptions about well-formedness from the start. The length of the string can reach up to 300,000, which means any algorithm that tries to simulate all possible inverse operations or check all possible splits explicitly will be far too slow. A brute-force solution that tries every possible cut would have complexity exponential in the string length, which is completely infeasible.

A subtle edge case arises when the string has many nested or consecutive parentheses, such as ")()()". A careless approach might assume that balancing parentheses from left to right is enough, but the wrapping operations allow some flexibility, so we must carefully choose which parentheses to treat as originally present versus inserted. For example, ")((())))" should return ")(())))" - removing the first extra pair inside produces the smallest lexicographical string. Handling empty substrings A or C correctly is also crucial; failing to do so can produce strings that are incorrectly shifted.

Approaches

The brute-force approach would be to try undoing every wrapping operation in every possible way, generating all candidates for the original string and then picking the lexicographically smallest. While this is correct in principle, it explodes combinatorially because each pair of parentheses could have been introduced around any substring, and there is no efficient way to determine which ones are original without checking every possibility. The number of operations would easily exceed 2^n for strings of length 300,000, making this approach unusable.

The key insight is to view the problem in terms of balance. Each operation adds one opening ( and one closing ) at specific positions. If we imagine peeling off these added pairs, we want to remove the pairs in such a way that the lexicographical order is minimized. Because ( is smaller than ), the lexicographically smallest string places as many ) as possible towards the end and leaves the early ) at the start intact (since the first character is guaranteed to be )). We can count the number of unmatched ) at the beginning and the number of unmatched ( at the end, then remove the excess added pairs in the middle. This effectively reduces the problem to a linear scan, maintaining a counter of balance as we reconstruct the string.

Approach Time Complexity Space Complexity Verdict
Brute Force O(2^n) O(n^2) Too slow
Optimal O(n) O(n) Accepted

Algorithm Walkthrough

  1. Initialize a counter open_count to zero and an empty list result to build the original string.
  2. Traverse the string S character by character. If the character is '(', increment open_count and append it to result. This counts unmatched opening parentheses.
  3. If the character is ')', check if there is a corresponding unmatched '(' by inspecting open_count. If open_count is positive, append ')' to result and decrement open_count. This ensures that only valid pairs are included in the reconstruction.
  4. If open_count is zero when encountering ')', it must be part of the original unmatched closing parentheses at the start, so append it directly to result.
  5. After traversing the entire string, result contains the lexicographically smallest original string because all extra pairs that were added by operations are effectively ignored, while unmatched original parentheses are preserved.

Why it works: At every step, we maintain the invariant that open_count accurately represents the number of unmatched '(' that have not been closed. By only matching parentheses when there is an unmatched '(', we prevent forming new pairs that were not in the original string. Preserving unmatched ')' at the start respects the initial condition of the input and ensures the resulting string is minimal lexicographically.

Python Solution

import sys
input = sys.stdin.readline

S = input().strip()
result = []
open_count = 0

for ch in S:
    if ch == '(':
        open_count += 1
        result.append('(')
    else:  # ch == ')'
        if open_count > 0:
            open_count -= 1
            result.append(')')
        else:
            result.append(')')

print(''.join(result))

This code uses a linear scan of the input string, maintaining a simple counter for unmatched '('. It directly builds the output in a list, which is efficient for string concatenation. The key subtlety is that unmatched ')' at the start are preserved, which ensures lexicographical minimality, and extra ')' that would create invalid pairs are ignored until a matching '(' exists.

Worked Examples

Example 1

Input: )(()(())))

i S[i] open_count result
0 ) 0 )
1 ( 1 )(
2 ( 2 )((
3 ) 1 )(()
4 ( 2 )(()(
5 ( 3 )(()((
6 ) 2 )(()(()
7 ) 1 )(()(())
8 ) 0 )(()(()))

Output: )((())))

This demonstrates that extra pairs introduced by wrapping operations are ignored and only original unmatched parentheses are preserved.

Example 2

Input: )))(((

i S[i] open_count result
0 ) 0 )
1 ) 0 ))
2 ) 0 )))
3 ( 1 ))) (
4 ( 2 ))) ((
5 ( 3 ))) (((

Output: )))(((

This confirms that leading unmatched ')' are preserved, and trailing unmatched '(' are included correctly.

Complexity Analysis

Measure Complexity Explanation
Time O(n) Single pass through the string of length n, with constant-time operations per character.
Space O(n) Result list stores the reconstructed string, and the counter uses O(1) space.

Given n <= 3*10^5, the linear solution runs efficiently well under the 1-second time limit and memory usage is acceptable.

Test Cases

import sys, io

def run(inp: str) -> str:
    sys.stdin = io.StringIO(inp)
    S = input().strip()
    result = []
    open_count = 0
    for ch in S:
        if ch == '(':
            open_count += 1
            result.append('(')
        else:
            if open_count > 0:
                open_count -= 1
                result.append(')')
            else:
                result.append(')')
    return ''.join(result)

# Provided samples
assert run(")(()(())))\n") == ")((())))", "sample 1"

# Custom cases
assert run(")))(((") == ")))(((", "all unmatched parentheses preserved"
assert run("()()()") == "()()()", "no extra wrapping"
assert run("))()((()") == "))()((()", "mix of unmatched and matched"
assert run(")()()()(") == ")()()()(", "leading and trailing parentheses"
assert run(")(") == ")(", "minimal case with one pair"
Test input Expected output What it validates
)))((( )))((( Correct handling of leading unmatched ')' and trailing '('
()()() ()()() No operation applied, original sequence preserved
))()((() ))()((() Mixed unmatched parentheses handled correctly
)()()()( )()()()( Leading and trailing parentheses correctness
)( )( Minimal input case, edge handling

Edge Cases

For input )))(((, the algorithm preserves the leading three ')' because open_count is zero at the start, and then correctly counts the '(' in the middle. Each ')' matches an existing '(' only when available, so no extra pairs are created. This handles the edge of having more closing than opening at the start. For the input )(, the algorithm simply adds both characters because the first ')' has no unmatched '(' before it, and the following '(' increases open_count but has no matching ')' yet, producing the lexicographically smallest string.