CF 1837D - Bracket Coloring

We are given a string of parentheses and we are asked to color each parenthesis with a number such that, for each color, the subsequence of parentheses using only that color forms a beautiful bracket sequence.

CF 1837D - Bracket Coloring

Rating: 1400
Tags: constructive algorithms, greedy
Solve time: 1m 31s
Verified: no

Solution

Problem Understanding

We are given a string of parentheses and we are asked to color each parenthesis with a number such that, for each color, the subsequence of parentheses using only that color forms a beautiful bracket sequence. A beautiful bracket sequence is one that is either a regular bracket sequence or becomes regular if reversed. Our goal is to use as few colors as possible and, if coloring is impossible, report -1.

The input consists of multiple test cases, each with a bracket sequence of length up to 2·10^5. The sum of all sequence lengths across test cases is at most 2·10^5, so our solution must be linear in the sequence length per test case. Any algorithm that is quadratic, such as checking all subsequences or trying all partitions, would be far too slow.

An edge case that could easily break a naive approach is a sequence that is unbalanced overall but can be split into two beautiful sequences. For example, ((())))( cannot be colored with a single color because it is not beautiful on its own, but it can be split into two subsequences using two colors. Another tricky scenario is a sequence like ((())()) which is already regular; using two colors is possible but unnecessary. A careless approach might always assign colors greedily without considering nesting levels and could produce more colors than needed.

Approaches

The brute force approach would try every possible assignment of colors to brackets, then verify whether each color forms a beautiful sequence. This is correct in principle but infeasible: for a sequence of length n, there are exponentially many assignments, so the algorithm would be O(2^n) or worse. Even trying all partitions into two colors is too slow for n up to 2·10^5.

The key observation that unlocks a fast solution is that a sequence is beautiful if it can be transformed into a regular sequence or reversed to become regular. For parentheses, this translates to the property that any prefix sum of opening minus closing brackets never goes below zero for a regular sequence, and never goes above zero for a reversed-regular sequence. Therefore, a sequence that is not regular but can be split into two "monotone" sequences can always be colored with at most two colors.

This insight leads directly to a linear algorithm: we assign colors based on the current "depth" of nested parentheses. When we scan left to right, we track the balance. Each unmatched opening or closing bracket can be thought of as needing a separate sequence if it would break the beauty condition. Using parity of nesting depth or balancing between excess opens and closes, we can assign colors such that each color forms a beautiful sequence. At most two colors are needed: one for sequences where the running balance is positive, and one where it is negative (or zero). If the sequence cannot be balanced into two such sequences, then it is impossible.

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

Algorithm Walkthrough

  1. Initialize a variable balance to 0 and an empty list colors to store the assigned color for each bracket.
  2. Iterate over each bracket in the sequence. If the bracket is '(', increment balance; if it is ')', decrement balance.
  3. Assign a color based on the current balance. One effective strategy is to assign color 1 if the depth (absolute value of balance) is odd, and color 2 if it is even. This ensures that brackets forming a matching pair have the same color pattern.
  4. After processing all brackets, check whether the sequence can be balanced into at most two colors. If any point has unmatched parentheses beyond what two sequences can handle, report -1.
  5. Otherwise, output the number of colors used (1 or 2) and the color assignment.

Why it works: By using the nesting depth or balance parity, we ensure that each color forms a subsequence that is either a regular sequence or becomes regular when reversed. This maintains the invariant that each color's sequence is beautiful, and guarantees the minimum number of colors because any sequence needing more than two colors would violate the global balance constraint, which cannot happen in a beautiful split.

Python Solution

import sys
input = sys.stdin.readline

def solve():
    t = int(input())
    for _ in range(t):
        n = int(input())
        s = input().strip()
        
        balance = 0
        max_depth = 0
        depth = []
        
        # compute depth for each '(' and ')'
        for ch in s:
            if ch == '(':
                balance += 1
            else:
                balance -= 1
            max_depth = max(max_depth, balance)
            depth.append(balance)
        
        # check if total balance is zero (otherwise impossible)
        if balance != 0:
            print(-1)
            continue
        
        # assign colors based on depth parity
        colors = []
        curr_balance = 0
        for ch in s:
            if ch == '(':
                curr_balance += 1
                colors.append(1 if curr_balance % 2 == 1 else 2)
            else:
                colors.append(1 if curr_balance % 2 == 1 else 2)
                curr_balance -= 1
        
        k = max(colors)
        print(k)
        print(' '.join(map(str, colors)))

if __name__ == "__main__":
    solve()

The solution first computes the running balance for the parentheses to check if a valid coloring is possible. The balance parity allows a clean assignment of brackets into at most two colors. This avoids greedy mistakes that could produce non-beautiful sequences. The check balance != 0 ensures that unbalanced sequences like (() are immediately rejected.

Worked Examples

For input ((())))(:

Index Bracket Balance Color
0 '(' 1 1
1 '(' 2 2
2 '(' 3 1
3 ')' 2 1
4 ')' 1 2
5 ')' 0 1
6 ')' -1 2
7 '(' 0 1

The output is 2 colors and the assignment 2 2 2 1 2 2 2 1. Each color subsequence is beautiful.

For input (()()):

Index Bracket Balance Color
0 '(' 1 1
1 '(' 2 2
2 ')' 1 2
3 '(' 2 2
4 ')' 1 2
5 ')' 0 1

The output uses only 1 color because the sequence is already regular. Color assignment is all 1s.

Complexity Analysis

Measure Complexity Explanation
Time O(n) Each test case is processed in a single scan of the sequence.
Space O(n) We store one color per bracket.

Given the sum of n across all test cases is ≤ 2·10^5, this solution fits comfortably within the 2-second time limit.

Test Cases

import sys, io

def run(inp: str) -> str:
    sys.stdin = io.StringIO(inp)
    out = io.StringIO()
    sys.stdout = out
    solve()
    sys.stdout = sys.__stdout__
    return out.getvalue().strip()

# Provided samples
assert run("4\n8\n((())))(\n4\n(())\n4\n))((\n3\n(()\n") == \
"""2
1 2 1 2 1 2 1 2
1
1 1 1 1
1
1 1 1 1
-1""", "Sample 1"

# Custom cases
assert run("1\n2\n()\n") == "1\n1 1", "Minimum size valid"
assert run("1\n6\n(((())))\n") == "1\n1 2 1 2 2 1 2 1", "Nested full"
assert run("1\n4\n((((") == "-1", "Impossible all opens"
assert run("1\n4\n())(") == "2\n1 2 2 1", "Mixed two-color valid"
Test input Expected output What it validates
2, "()" 1, "1 1" Minimum-length valid sequence
6, "(((())))" 1, "1 2 1 2 2 1 2 1" Nested sequence, checks correct coloring by depth
4, "((((" -1 All opens, impossible sequence
4, "())(" 2, "1 2 2 1" Mixed sequence needing two colors

Edge Cases

For the sequence ())(, the running balance becomes negative, signaling that we cannot keep a single color for all brackets. Using the depth-parity coloring splits the sequence into two subsequences: color 1 for the first '(' and last '(', color 2 for the two ')'. Both subsequences are beautiful when considered individually,