Правильные скобочные последовательности
Определения
<wikitex>
| Определение: |
| Скобочная последовательность — класс комбинаторных объектов, представляющих собой последовательность скобочных символов. |
Примеры скобочных последовательностей
- $(())))($
- $)()()))()(()())$
| Определение: |
Правильная скобочная последовательность — частный случай скобочной последовательности, определяющийся следующими образами:
|
Примеры правильных скобочный последовательностей
- $((()()()()))$
- $(())(()())$
</wikitex>
Алгоритм проверки правильности скобочной последовательности
<wikitex> Пусть нам дана скобочная последовательность, записанная в строку $s$. Возьмем переменную $counter$, $counter = 0$. Будем последовательно перебирать все символы этой строки. Если мы встречаем открывающуюся скобку, то увеличиваем $counter$ на $1$, закрывающую — уменьшаем на $1$. Если на протяжении всего перебора $counter$ было неотрицательным и после завершения осталось нулем, то скобочная последовательность правильна. </wikitex>
Псевдокод
<wikitex>
check(s)
counter = 0
for i = 1 to length(s)
if s[i] == '('
counter++
else
counter--
if counter < 0
return false
if counter == 0
return true
else
return false
Надо отметить, что скобочные последовательности могут состоять не только из одного типа скобок. При этом недопустимо такое расположение, когда один тип скобок закрывает другой: </wikitex>
Примеры скобочных последовательностей с несколькими типами скобок
<wikitex>
- $()[()]\{()()[]\}$ — верно
- $[(]\{\})$ — неверно
В этом случае для проверки надо будет использовать стек. </wikitex>
Лексикографический порядок порядок правильных скобочных последовательностей
<wikitex> Для того, чтобы определить лексикографический порядок для правильных скобочных последовательностей, надо установить порядок на алфавите, например так '$($' < '$)$'. Для последовательностей с разным типом скобок надо определять свой порядок в зависимости от числа скобок, причем любая открывающаяся скобка должна быть меньше закрывающейся, например "$($" < "$[$" < "$)$" < "$]$". </wikitex>
Примеры лексикографического порядка для $n$ и $k$, где $n$ — число открывающихся скобок, а $k$ — число видов скобок
<wikitex>
| $n = 3$ | $k = 1$ | |||
| $((()))$ | $(()())$ | $(())()$ | $()(())$ | $()()()$ |
| $n = 2$ | $k = 2$ | ||
| $()[]$ | $([])$ | $[()]$ | $[]()$ |
Алгоритм генерации лексикографического порядка будет предложен ниже. </wikitex>
Количество правильных скобочных последовательностей. Числа Каталана
<wikitex> Количество правильных скобочных последовательностей со скобками одного типа совпадает с числами Каталана.
| Определение: |
Числа Каталана — последовательность чисел, выражающих:
|
</wikitex>
Рекурентная формула
<wikitex> $C_n = \sum_{i = 0}^{n - 1} C_i C_{n - 1 - i}$
Рекуррентную формулу легко вывести из задачи о правильных скобочных последовательностях.
Самой левой открывающей скобке $l$ соответствует определённая закрывающая скобка $r$, которая разбивает формулу на две части, каждая из которых в свою очередь является правильной скобочной последовательностью. Поэтому, если мы обозначим $i = r - l - 1$, то для любого фиксированного $r$ будет ровно $C_i C_{n-1-i}$ способов. Суммируя это по всем допустимым i, мы и получаем рекуррентную зависимость на $C_n$. </wikitex>
Аналитическая формула
<wikitex> $ C_n = \frac{1}{n+1} C_{2n}^{n} $
(здесь через $C_n^k$ обозначен, как обычно, биномиальный коэффициент).
Эту формулу проще всего вывести из задачи о монотонных путях. Общее количество монотонных путей в решётке размером $n \times n$ равно $C_{2n}^{n}$. Теперь посчитаем количество монотонных путей, пересекающих диагональ. Рассмотрим какой-либо из таких путей, и найдём первое ребро, которое стоит выше диагонали. Отразим относительно диагонали весь путь, идущий после этого ребра. В результате получим монотонный путь в решётке $(n - 1) \times (n + 1)$. Но, с другой стороны, любой монотонный путь в решётке $(n - 1) \times (n + 1)$ обязательно пересекает диагональ, следовательно, он получен как раз таким способом из какого-либо (причём единственного) монотонного пути, пересекающего диагональ, в решётке $n \times n$. Монотонных путей в решётке $(n - 1) \times (n + 1)$ имеется $C_{2n}^{n-1}$. В результате получаем формулу:
$ C_n = C_{2n}^{n} - C_{2n}^{n-1} = \frac{1}{n+1} C_{2n}^{n}$ </wikitex>
Алгоритмы генерации
<wikitex>
Генерация следующей скобочной последовательности
<wikitex> Пусть нам известна строка $s$, представляющая собой правильную скобочную последовательность. Нам необходимо вывести следующую скобочную последовательность, а если ее нет, то — "No solution". Чтобы получить следующую скобочную последовательность надо найти последнюю открывающуюся скобку, которую можно заменить, заменить ее на закрывающуюся, а оставшиеся в конце скобки (если они есть) заменить на минимально возможную последовательность скобок:
next(s)
counter_close = 0
counter_open = 0
for i = length(s) downto 1
if s[i] == '('
counter_open++
if counter_close > counter_open
break
else
counter_close++
delete(s, length(s) - counter_open - counter_close + 1, counter_close + l)
if s == ""
return false
s = s +')'
for j = 1 to counter_open
s = s + '('
for j = 1 to counter_close - 1
s = s + ')'
return true
Если эта функция после выполнения выводит $true$, тогда надо напечатать полученную строку $s$, если $false$, то следует вывести "No solution". </wikitex>
Получение лексикографического порядка
<wikitex> Пусть нам известно число $n$. Надо вывести все правильные скобочные последовательности в лексикографическом порядке с $n$ открывающимися скобками:
order(n)
s = ""
if n == 0
result(s)
else
for j = 1 to n
s = s + '('
for j = 1 to n
s = s + ')'
result(s)
t = next(s)
while t != false
result(s)
t = next(s)
return
Также с помощью этого алгоритма можно получить скобочную последовательность по номеру и номер по скобочной последовательности, добавив сравнение с нужной последовательностью и счетчик. Но это далеко не самый оптимальный алгоритм для подобного типа задач и он не будет нормально работать для больших $n$. </wikitex>
Получение номера последовательности
<wikitex> Пусть $n$ — количество пар скобок в последовательности. Требуется по заданной правильной скобочной последовательности найти её номер в списке лексикографически упорядоченных правильных скобочных последовательностей.
Научимся считать вспомогательную динамику $d[i][j]$, где $i$ — длина скобочной последовательности (она "полуправильная": всякой закрывающей скобке соответствует парная открывающая, но не все открытые скобки закрыты), $j$ — баланс (т.е. разность между количеством открывающих и закрывающих скобок), $d[i][j]$ — количество таких последовательностей. При подсчёте этой динамики мы считаем, что скобки бывают только одного типа.
Считать эту динамику можно следующим образом. Пусть $d[i][j]$ — величина, которую мы хотим посчитать. Если $i = 0$, то ответ понятен сразу: $d[0][0] = 1$, все остальные $d[0][j] = 0$. Пусть теперь $i > 0$, тогда переберём, чему мог быть равен последний символ этой последовательности. Если он был равен '(', то до этого символа мы находились в состоянии $(i-1,j-1)$. Если он был равен ')', то предыдущим было состояние $(i-1,j+1)$. Таким образом, получаем формулу:
$d[i][j] = d[i-1][j-1] + d[i-1][j+1]$
(считается, что все значения $d[i][j]$ при отрицательном $j$ равны нулю). Таким образом, эту динамику мы можем посчитать за $O(n^2)$.
Перейдём теперь к решению самой задачи. Сначала пусть допустимы только скобки одного типа:
getNumber(s)
num = 0
depth = 0
for i = 0 to 2 * n - 1
if s[i] == '('
depth++
else
num += d[2 * n - i - 1][depth + 1]
depth--
return num
Пусть теперь разрешены скобки $k$ типов. Тогда при рассмотрении текущего символа $s[i]$ до пересчёта $\rm depth$ мы должны перебирать все скобки, которые меньше текущего символа в установленном ранее порядке, пробовать ставить эту скобку в текущую позицию (получая тем самым новый баланс $\rm ndepth = \rm depth \pm 1$), и прибавлять к ответу количество соответствующих "хвостов" - завершений (которые имеют длину $2n - i - 1$, баланс $\rm ndepth$ и $k$ типов скобок). Утверждается, что формула для этого количества имеет вид:
$d[2n - i - 1][ndepth] \cdot k^{(2n - i - 1 - ndepth) / 2}$
Эта формула выводится из следующих соображений. Сначала мы "забываем" про то, что скобки бывают нескольких типов, и просто берём ответ из $d[2n - i - 1][{\rm ndepth}]$. Теперь посчитаем, как изменится ответ из-за наличия $k$ типов скобок. У нас имеется $2n - i - 1$ неопределённых позиций, из которых $\rm ndepth$ являются скобками, закрывающими какие-то из открытых ранее, — значит, тип таких скобок мы варьировать не можем. А вот все остальные скобки (а их будет $(2n - i - 1 - {\rm ndepth}) / 2$ пар) могут быть любого из $k$ типов, поэтому ответ умножается на эту степень числа $k$.
Сложность данного алгоритма $O(n^2 + n \cdot k)$. </wikitex>
Получение k-й последовательности
<wikitex> Пусть $n$ — количество пар скобок в последовательности. В данной задаче по заданному $k$ требуется найти $k$-ую правильную скобочную последовательность в списке лексикографически упорядоченных последовательностей.
Как и в предыдущем разделе, посчитаем динамику $d[i][j]$ — количество правильных скобочных последовательностей длины $i$ с балансом $j$.
Пусть сначала допустимы только скобки одного типа:
getSequence(n, k)
depth = 0
s = ""
for i = 0 to 2 * n - 1
if d[2 * n - (i + 1)][depth + 1] >= k
s += '('
depth++
else
k -= d[2 * n - (i + 1)][depth + 1]
s += ')'
depth--
return s
Пусть теперь разрешён не один, а $k$ типов скобок. Тогда алгоритм решения будет отличаться от предыдущего случая только тем, что мы должны домножать значение $d[i + 1][\rm ndepth]$ на величину $k^{(2n - i - 1 - \rm ndepth) / 2}$, чтобы учесть, что в этом остатке могли быть скобки различных типов, а парных скобок в этом остатке будет только $2n - i - 1 - \rm ndepth$, поскольку $\rm ndepth$ скобок являются закрывающими для открывающих скобок, находящихся вне этого остатка (а потому их типы мы варьировать не можем).
Сложность данного алгоритма $O(n^2 + n \cdot k)$. </wikitex>