E135. Правильные скобочные последовательности

e-maxx algorithm original: C/C++ #algorithm #combinatorics #emaxx #math
선택한 UI 언어에 맞게 문제 텍스트를 러시아어에서 번역합니다. 코드는 변경하지 않습니다.

Источник: e-maxx.ru/algo, страница PDF 432.

Правильной скобочной последовательностью называется 문자열, состоящая только из символов "скобки" (чаще всего рассматриваются только круглые скобки, но здесь будет рассматриваться и общий случай нескольких типов скобок), где каждой закрывающей скобке найдётся соответствующая открывающая (причём того же типа). Здесь мы рассмотрим классические задачи на правильные скобочные последовательности (далее для краткости просто "последовательности"): проверка на правильность, количество последовательностей, генерация всех последовательностей, нахождение лексико그래프ически следующей последовательности, нахождение

-

ой последовательности в отсортированном списке всех последовательностей, и, наоборот, 정의 номера последовательности. Каждая из задач рассмотрена в двух случаях — когда разрешены скобки только одного типа, и когда нескольких типов.

Проверка на правильность

Пусть сначала разрешены скобки только одного типа, тогда проверить последовательность на правильность можно

очень простым 알고리즘ом. Пусть

— это текущее количество открытых скобок. Изначально

. Будем двигаться по строке слева направо, если текущая скобка открывающая, то увеличим

на единицу,

иначе уменьшим. Если при этом когда-то получалось отрицательное number, или в конце работы 알고리즘а отлично от нуля, то данная 문자열 не является правильной скобочной последовательностью, иначе является. Если допустимы скобки нескольких типов, то 알고리즘 нужно изменить. Вместо счётчика

следует создать стек,

в который будем класть открывающие скобки по мере поступления. Если текущий символ строки — открывающая скобка, то кладём его в стек, а если закрывающая — то проверяем, что стек не пуст, и что на его вершине лежит скобка того же типа, что и текущая, и затем достаём эту скобку из стека. Если какое-либо из условий не выполнилось, или в конце работы 알고리즘а стек остался не пуст, то последовательность не является правильной скобочной, иначе является.

Таким образом, обе эти задачи мы научились решать за время

.

Количество последовательностей

Формула

Количество правильных скобочных последовательностей с одним типом скобок можно вычислить как

number Каталана. Т.е. если есть

пар скобок (문자열 длины

), то количество будет равно:

Пусть теперь имеется не один, а

типов скобок. Тогда каждая пара скобок независимо от остальных может

принимать один из

типов, а потому мы получаем такую формулу:

dynamic programming

С другой стороны, к этой задаче можно подойти и с точки зрения динамического

программирования. Пусть

— количество правильных скобочных последовательностей из

пар

скобок. Заметим, что в первой позиции всегда будет стоять открывающая скобка. Понятно, что внутри этой пары скобок стоит какая-то правильная скобочная последовательность; аналогично, после этой пары скобок также стоит правильная скобочная последовательность. Теперь чтобы посчитать

, переберём, сколько пар скобок

будет стоять внутри этой первой пары, тогда, соответственно,

пара скобок будет стоять после этой

первой пары. Следовательно, формула для

имеет вид:

Начальное значение для этой рекуррентной формулы — это

.

Нахождение всех последовательностей

Иногда it is required find и вывести все правильные скобочные последовательности указанной длины

(в данном

случае

— это длина строки). Для этого можно начать с лексико그래프ически первой последовательности

, а затем находить

каждый раз лексико그래프ически следующую последовательность с помощью 알고리즘а, описанного в следующем разделе.

Но если 제약 не очень большие (

до

), то можно поступить значительно проще. Найдём

всевозможные перестановки этих скобок (для этого удобно использовать функцию next_permutation()), их будет

,

и каждую проверим на правильность вышеописанным 알고리즘ом, и в случае правильности выведем текущую последовательность. Также процесс нахождения всех последовательностей можно оформить в виде рекурсивного перебора с отсечениями (что в идеале можно довести по скорости работы до первого 알고리즘а).

Нахождение следующей последовательности

Здесь рассматривается только случай одного типа скобок. По заданной правильной скобочной последовательности it is required find правильную скобочную последовательность, которая находится следующей в лексико그래프ическом порядке после текущей (или выдать "No solution", если такой не существует). Понятно, что в целом 알고리즘 выглядит следующим образом: найдем такую самую правую открывающую скобку, которую мы имеем право заменить на закрывающую (так, чтобы в этом месте правильность не нарушалась), а всю оставшуюся справа строку заменим на лексико그래프ически минимальную: т.е. сколько-то открывающих скобок, затем все оставшиеся закрывающие скобки. Иными словами, мы пытаемся оставить без изменения как можно более длинный префикс исходной последовательности, а в суффиксе эту последовательность заменяем на лексико그래프ически минимальную. Осталось научиться искать эту самую позицию первого изменения. Для этого будем идти по строке справа налево

и поддерживать баланс

открытых и закрытых скобок (при встрече открывающей скобки будем

уменьшать

, а при закрывающей — увеличивать). Если в какой-то момент мы стоим на открывающей скобке, а баланс после обработки этого символа больше нуля, то мы нашли самую правую позицию, от которой мы можем

начать изменять последовательность (в самом деле,

означает, что слева имеется не закрытая ещё

скобка). Поставим в текущую позицию закрывающую скобку, затем максимально возможное количество открывающих скобок, а затем все оставшиеся закрывающие скобки, — ответ найден. Если мы просмотрели всю строку и так и не нашли подходящую позицию, то текущая последовательность — максимальна, и ответа не существует. 구현 알고리즘а:

string s;

cin >> s;

int n = (int) s.length();

string ans = "No solution";

for (int i=n-1, depth=0; i>=0; --i) {
if (s[i] == '(')

--depth;

else

++depth;

if (s[i] == '(' && depth > 0) {

--depth;

int open = (n-i-1 - depth) / 2;
int close = n-i-1 - open;

ans = s.substr(0,i) + ')' + string ('(', open) + string

(')', close);

break;

} }

cout << ans;

Таким образом, мы решили эту задачу за

.

Номер последовательности

Здесь пусть

— количество пар скобок в последовательности. it is required по заданной правильной скобочной последовательности find её номер в списке лексико그래프ически упорядоченных правильных скобочных последовательностей.

Научимся считать вспомогательную динамику

, где

— длина скобочной последовательности

(она "полуправильна": всякой закрывающей скобке имеется парная открывающая, но не все открытые скобки закрыты), — баланс (т.е. разность между количеством открывающих и закрывающих скобок),

— количество

таких последовательностей. При подсчёте этой динамики мы считаем, что скобки бывают только одного типа.

Считать эту динамику можно следующим образом. Пусть

— величина, которую мы хотим посчитать. Если

, то ответ понятен сразу:

, все остальные

. Пусть теперь

, тогда

мысленно переберём, чему был равен последний символ этой последовательности. Если он был равен '(', то до

этого символа мы находились в состоянии

. Если он был равен ')', то предыдущим было

состояние

. Таким образом, получаем формулу:

(считается, что все значения

при отрицательном

равны нулю). Таким образом, эту динамику мы

можем посчитать за

. Перейдём теперь к решению самой задачи. Сначала пусть допустимы только скобки одного типа. Заведём счётчик

глубины вложенности в скобки, и

будем двигаться по последовательности слева направо. Если текущий символ

(

) равен '(', то

мы увеличиваем

на 1 и переходим к следующему символу. Если же текущий символ равен ')', то мы

должны прибавить к ответу

, тем самым given, что в этой позиции мог бы

стоять символ '(' (который бы привёл к лексико그래프ически меньшей последовательности, чем текущая); затем

мы уменьшаем

на единицу.

Пусть теперь разрешены скобки нескольких

типов. Тогда при рассмотрении текущего символа

до

пересчёта

мы должны перебирать все скобки, которые меньше текущего символа, пробовать ставить эту скобку

в текущую позицию (получая тем самым новый баланс

), и и прибавлять к ответу

количество соответствующих "хвостов" - завершений (которые имеют длину

, баланс

и

типов скобок). Утверждается, что формула для этого количества имеет вид: Эта формула выводится из следующих соображений. Сначала мы "забываем" про то, что скобки бывают нескольких

типов, и просто берём ответ из

. Теперь посчитаем, как изменится ответ из-за наличия

типов скобок. У нас имеется

неопределённых позиций, из которых

являются

скобками, закрывающими какие-то из открытых ранее, — значит, тип таких скобок мы варьировать не можем. А вот

все остальные скобки (а их будет

пар) могут быть любого из

типов, поэтому

ответ умножается на эту степень числа

.

Нахождение

-ой последовательности

Здесь пусть

— количество пар скобок в последовательности. В данной задаче по заданному

it is required find

-

ую правильную скобочную последовательность в списке лексико그래프ически упорядоченных последовательностей.

Как и в предыдущем разделе, посчитаем динамику

— количество правильных

скобочных последовательностей длины

с балансом

. Пусть сначала допустимы только скобки одного типа.

Будем двигаться по символам искомой строки, с

-го по

-ый. Как и в предыдущей задаче, будем хранить

счётчик

— текущую глубину вложенности в скобки. В каждой текущей позиции будем перебирать возможный символ - открывающую скобку или закрывающую. Пусть мы хотим поставить сюда открывающую скобку,

тогда мы должны посмотреть на значение

. Если оно

, то мы ставим в текущую

позицию открывающую скобку, увеличиваем

на единицу и переходим к следующему символу. Иначе мы

отнимаем от

величину

, ставим закрывающую скобку и уменьшаем значение

. В

конце концов мы и получим искомую скобочную последовательность. 구현 на языке Java с использованием длинной арифметики:

int n;  BigInteger k;  // 입력ные данные

BigInteger d[][] = new BigInteger [n*2+1][n+1];

for (int i=0; i<=n*2; ++i)
for (int j=0; j<=n; ++j)

d[i][j] = BigInteger.ZERO;

d[0][0] = BigInteger.ONE;

for (int i=0; i<n*2; ++i)
for (int j=0; j<=n; ++j) {
if (j+1 <= n)

d[i+1][j+1] = d[i+1][j+1].add( d[i][j] );

if (j > 0)

d[i+1][j-1] = d[i+1][j-1].add( d[i][j] );

}

String ans = new String();

if (k.compareTo( d[n*2][0] ) > 0)

ans = "No solution";

else {

int depth = 0;
for (int i=n*2-1; i>=0; --i)
if (depth+1 <= n && d[i][depth+1].compareTo( k ) >= 0) {

ans += '(';

++depth;

}

else {

ans += ')';

if (depth+1 <= n)

k = k.subtract( d[i][depth+1] );

--depth;

} }

Пусть теперь разрешён не один, а

типов скобок. Тогда 알고리즘 решения будет отличаться от предыдущего

случая только тем, что мы должны домножать значение

на величину

, чтобы учесть, что в этом остатке могли быть скобки различных типов, а парных скобок в

этом остатке будет только

, поскольку

скобок являются закрывающими

для открывающих скобок, находящихся вне этого остатка (а потому их типы мы варьировать не можем). 구현 на языке Java для случая двух типов скобок - круглых и квадратных:

int n;  BigInteger k;  // 입력ные данные

BigInteger d[][] = new BigInteger [n*2+1][n+1];

for (int i=0; i<=n*2; ++i)
for (int j=0; j<=n; ++j)

d[i][j] = BigInteger.ZERO;

d[0][0] = BigInteger.ONE;

for (int i=0; i<n*2; ++i)
for (int j=0; j<=n; ++j) {
if (j+1 <= n)

d[i+1][j+1] = d[i+1][j+1].add( d[i][j] );

if (j > 0)

d[i+1][j-1] = d[i+1][j-1].add( d[i][j] );

}

String ans = new String();

int depth = 0;

char [] stack = new char[n*2];

int stacksz = 0;
for (int i=n*2-1; i>=0; --i) {

BigInteger cur;

// '('

if (depth+1 <= n)

cur = d[i][depth+1].shiftLeft( (i-depth-1)/2 );

else

cur = BigInteger.ZERO;

if (cur.compareTo( k ) >= 0) {

ans += '(';

stack[stacksz++] = '(';

++depth;

continue;

}

k = k.subtract( cur );

// ')'

if (stacksz > 0 && stack[stacksz-1] == '(' && depth-1 >= 0)

cur = d[i][depth-1].shiftLeft( (i-depth+1)/2 );

else

cur = BigInteger.ZERO;

if (cur.compareTo( k ) >= 0) {

ans += ')';

--stacksz;

--depth;

continue;

}

k = k.subtract( cur );

// '['

if (depth+1 <= n)

cur = d[i][depth+1].shiftLeft( (i-depth-1)/2 );

else

cur = BigInteger.ZERO;

if (cur.compareTo( k ) >= 0) {

ans += '[';

stack[stacksz++] = '[';

++depth;

continue;

}

k = k.subtract( cur );

// ']'

ans += ']';

--stacksz;

--depth;

}

Количество помеченных 그래프ов

given number

вершин. it is required посчитать количество

различных помеченных 그래프ов с

vertexми (т.

е. вершины 그래프а помечены различными числами от

до

, и 그래프ы сравниваются с учётом этой покраски

вершин). Рёбра 그래프а неориентированы, петли и кратные рёбра запрещены. Рассмотрим множество всех возможных рёбер 그래프а. Для любого ребра

положим, что

(основываясь

на неориентированности 그래프а и отсутствии петель). Тогда множество всех возможных рёбер 그래프а имеет мощность , т.е. . Поскольку любой помеченный 그래프 однозначно определяется своими рёбрами, то количество помеченных 그래프ов с vertexми равно:

Количество связных помеченных 그래프ов

По сравнению с предыдущей задачей мы дополнительно накладываем ограничение, что 그래프 должен быть связным.

Обозначим искомое number через

. Научимся, наоборот, считать количество несвязных 그래프ов; тогда количество связных 그래프ов получится как минус найденное number. Более того, научимся считать количество корневых (т.е. с выделенной вершиной - корнем) несвязных 그래프ов; тогда количество несвязных 그래프ов будет получаться из него делением на . Заметим, что, так как 그래프 несвязный, то в нём найдётся компонента связности, внутри которой лежит корень, а остальной 그래프 будет представлять собой ещё несколько (как минимум одну) компонент связности.

Переберём количество

вершин в этой компоненте связности, содержащей корень (очевидно,

),

и найдём количество таких 그래프ов. Во-первых, мы должны выбрать

вершин из

, т.е. ответ умножается на

.

Во-вторых, компонента связности с корнем даёт множитель

. В-третьих, оставшийся 그래프 из

вершин является произвольным 그래프ом, а потому он даёт множитель

. Наконец, количество способов

выделить корень в компоненте связности из

вершин равно

. Итого, при фиксированном

количество

корневых несвязных 그래프ов равно:

Значит, количество несвязных 그래프ов с

vertexми равно: Наконец, искомое количество связных 그래프ов равно:

Количество помеченных 그래프ов с

компонентами связности

Основываясь на предыдущей формуле, научимся считать количество помеченных 그래프ов с

vertexми и

компонентами связности. Сделать это можно с помощью динамического программирования. Научимся считать

количество помеченных 그래프ов с

vertexми и

компонентами связности.

Научимся вычислять очередной element

, зная предыдущие значения. Воспользуемся стандартным

приёмом при решении таких задач: возьмём вершину с номером 1, она принадлежит какой-то компоненте, вот

эту компоненту мы и будем перебирать. Переберём размер

этой компоненты, тогда количество способов выбрать

такое множество вершин равно

(одну вершину — вершину 1 — перебирать не надо). Количество же

способов построить компоненту связности из

вершин мы уже умеем считать — это

. После удаления

этой компоненты из 그래프а у нас остаётся 그래프 с

vertexми и

компонентами связности, т.е. мы получили рекуррентную зависимость, по которой можно вычислять значения : Итого получаем 예제но такой код:

int d[n+1][k+1]; // изначально заполнен нулями

d[0][0][0] = 1;

for (int i=1; i<=n; ++i)
for (int j=1; j<=i && j<=k; ++j)
for (int s=1; s<=i; ++s)

d[i][j] += C[i-1][s-1] * conn[s] * d[i-s][j-1];

cout << d[n][k][n];

Разумеется, на практике, скорее всего, нужна будет Arbitrary-precision arithmetic.

C# 해법

자동 초안, 제출 전 검토
using System;
using System.Collections.Generic;
using System.Linq;

public static class AlgorithmDraft
{
    // Auto-generated C# draft from the original e-maxx C/C++ listing. Review before production use.
    string s;
    cin >> s;
    int n = (int) s.length();
    string ans = "No solution";
    for (int i=n-1, depth=0; i>=0; --i) {
            if (s[i] == '(')
                    --depth;
            else
                    ++depth;
            if (s[i] == '(' && depth > 0) {
                    --depth;
                    int open = (n-i-1 - depth) / 2;
                    int close = n-i-1 - open;
                    ans = s.substr(0,i) + ')' + string ('(', open) + string
    (')', close);
                    break;
            }
    }
    Console.WriteLine( ans;
    int n;  BigInteger k;  // входные данные
    BigInteger d[][] = new BigInteger [n*2+1][n+1];
    for (int i=0; i<=n*2; ++i)
            for (int j=0; j<=n; ++j)
                    d[i][j] = BigInteger.ZERO;
    d[0][0] = BigInteger.ONE;
    for (int i=0; i<n*2; ++i)
            for (int j=0; j<=n; ++j) {
                    if (j+1 <= n)
                            d[i+1][j+1] = d[i+1][j+1].add( d[i][j] );
                    if (j > 0)
                            d[i+1][j-1] = d[i+1][j-1].add( d[i][j] );
            }
    String ans = new String();
    if (k.compareTo( d[n*2][0] ) > 0)
            ans = "No solution";
    else {
            int depth = 0;
            for (int i=n*2-1; i>=0; --i)
                    if (depth+1 <= n && d[i][depth+1].compareTo( k ) >= 0) {
                            ans += '(';
                            ++depth;
                    }
                    else {
                            ans += ')';
                            if (depth+1 <= n)
                                    k = k.subtract( d[i][depth+1] );
                            --depth;
                    }
    }
    int n;  BigInteger k;  // входные данные
    BigInteger d[][] = new BigInteger [n*2+1][n+1];
    for (int i=0; i<=n*2; ++i)
            for (int j=0; j<=n; ++j)
                    d[i][j] = BigInteger.ZERO;
    d[0][0] = BigInteger.ONE;
    for (int i=0; i<n*2; ++i)
            for (int j=0; j<=n; ++j) {
                    if (j+1 <= n)
                            d[i+1][j+1] = d[i+1][j+1].add( d[i][j] );
                    if (j > 0)
                            d[i+1][j-1] = d[i+1][j-1].add( d[i][j] );
            }
    String ans = new String();
    int depth = 0;
    char [] stack = new char[n*2];
    int stacksz = 0;
    for (int i=n*2-1; i>=0; --i) {
            BigInteger cur;
            // '('
            if (depth+1 <= n)
                    cur = d[i][depth+1].shiftLeft( (i-depth-1)/2 );
            else
                    cur = BigInteger.ZERO;
            if (cur.compareTo( k ) >= 0) {
                    ans += '(';
                    stack[stacksz++] = '(';
                    ++depth;
                    continue;
            }
            k = k.subtract( cur );
            // ')'
            if (stacksz > 0 && stack[stacksz-1] == '(' && depth-1 >= 0)
                    cur = d[i][depth-1].shiftLeft( (i-depth+1)/2 );
            else
                    cur = BigInteger.ZERO;
            if (cur.compareTo( k ) >= 0) {
                    ans += ')';
                    --stacksz;
                    --depth;
                    continue;
            }
            k = k.subtract( cur );
            // '['
            if (depth+1 <= n)
                    cur = d[i][depth+1].shiftLeft( (i-depth-1)/2 );
            else
                    cur = BigInteger.ZERO;
            if (cur.compareTo( k ) >= 0) {
                    ans += '[';
                    stack[stacksz++] = '[';
                    ++depth;
                    continue;
            }
            k = k.subtract( cur );
            // ']'
            ans += ']';
            --stacksz;
            --depth;
    }
    int d[n+1][k+1]; // изначально заполнен нулями
    d[0][0][0] = 1;
    for (int i=1; i<=n; ++i)
            for (int j=1; j<=i && j<=k; ++j)
                    for (int s=1; s<=i; ++s)
                            d[i][j] += C[i-1][s-1] * conn[s] * d[i-s][j-1];
    Console.WriteLine( d[n][k][n];
}

C++ 해법

매칭됨/원본
string s;
cin >> s;
int n = (int) s.length();
string ans = "No solution";
for (int i=n-1, depth=0; i>=0; --i) {
        if (s[i] == '(')
                --depth;
        else
                ++depth;
        if (s[i] == '(' && depth > 0) {
                --depth;
                int open = (n-i-1 - depth) / 2;
                int close = n-i-1 - open;
                ans = s.substr(0,i) + ')' + string ('(', open) + string
(')', close);
                break;
        }
}
cout << ans;
int n;  BigInteger k;  // входные данные
BigInteger d[][] = new BigInteger [n*2+1][n+1];
for (int i=0; i<=n*2; ++i)
        for (int j=0; j<=n; ++j)
                d[i][j] = BigInteger.ZERO;
d[0][0] = BigInteger.ONE;
for (int i=0; i<n*2; ++i)
        for (int j=0; j<=n; ++j) {
                if (j+1 <= n)
                        d[i+1][j+1] = d[i+1][j+1].add( d[i][j] );
                if (j > 0)
                        d[i+1][j-1] = d[i+1][j-1].add( d[i][j] );
        }
String ans = new String();
if (k.compareTo( d[n*2][0] ) > 0)
        ans = "No solution";
else {
        int depth = 0;
        for (int i=n*2-1; i>=0; --i)
                if (depth+1 <= n && d[i][depth+1].compareTo( k ) >= 0) {
                        ans += '(';
                        ++depth;
                }
                else {
                        ans += ')';
                        if (depth+1 <= n)
                                k = k.subtract( d[i][depth+1] );
                        --depth;
                }
}
int n;  BigInteger k;  // входные данные
BigInteger d[][] = new BigInteger [n*2+1][n+1];
for (int i=0; i<=n*2; ++i)
        for (int j=0; j<=n; ++j)
                d[i][j] = BigInteger.ZERO;
d[0][0] = BigInteger.ONE;
for (int i=0; i<n*2; ++i)
        for (int j=0; j<=n; ++j) {
                if (j+1 <= n)
                        d[i+1][j+1] = d[i+1][j+1].add( d[i][j] );
                if (j > 0)
                        d[i+1][j-1] = d[i+1][j-1].add( d[i][j] );
        }
String ans = new String();
int depth = 0;
char [] stack = new char[n*2];
int stacksz = 0;
for (int i=n*2-1; i>=0; --i) {
        BigInteger cur;
        // '('
        if (depth+1 <= n)
                cur = d[i][depth+1].shiftLeft( (i-depth-1)/2 );
        else
                cur = BigInteger.ZERO;
        if (cur.compareTo( k ) >= 0) {
                ans += '(';
                stack[stacksz++] = '(';
                ++depth;
                continue;
        }
        k = k.subtract( cur );
        // ')'
        if (stacksz > 0 && stack[stacksz-1] == '(' && depth-1 >= 0)
                cur = d[i][depth-1].shiftLeft( (i-depth+1)/2 );
        else
                cur = BigInteger.ZERO;
        if (cur.compareTo( k ) >= 0) {
                ans += ')';
                --stacksz;
                --depth;
                continue;
        }
        k = k.subtract( cur );
        // '['
        if (depth+1 <= n)
                cur = d[i][depth+1].shiftLeft( (i-depth-1)/2 );
        else
                cur = BigInteger.ZERO;
        if (cur.compareTo( k ) >= 0) {
                ans += '[';
                stack[stacksz++] = '[';
                ++depth;
                continue;
        }
        k = k.subtract( cur );
        // ']'
        ans += ']';
        --stacksz;
        --depth;
}
int d[n+1][k+1]; // изначально заполнен нулями
d[0][0][0] = 1;
for (int i=1; i<=n; ++i)
        for (int j=1; j<=i && j<=k; ++j)
                for (int s=1; s<=i; ++s)
                        d[i][j] += C[i-1][s-1] * conn[s] * d[i-s][j-1];
cout << d[n][k][n];

Java 해법

자동 초안, 제출 전 검토
import java.util.*;
import java.math.*;

public class AlgorithmDraft {
    // Auto-generated Java draft from the original e-maxx C/C++ listing. Review before production use.
    string s;
    cin >> s;
    int n = (int) s.length();
    string ans = "No solution";
    for (int i=n-1, depth=0; i>=0; --i) {
            if (s[i] == '(')
                    --depth;
            else
                    ++depth;
            if (s[i] == '(' && depth > 0) {
                    --depth;
                    int open = (n-i-1 - depth) / 2;
                    int close = n-i-1 - open;
                    ans = s.substr(0,i) + ')' + string ('(', open) + string
    (')', close);
                    break;
            }
    }
    System.out.println( ans;
    int n;  BigInteger k;  // входные данные
    BigInteger d[][] = new BigInteger [n*2+1][n+1];
    for (int i=0; i<=n*2; ++i)
            for (int j=0; j<=n; ++j)
                    d[i][j] = BigInteger.ZERO;
    d[0][0] = BigInteger.ONE;
    for (int i=0; i<n*2; ++i)
            for (int j=0; j<=n; ++j) {
                    if (j+1 <= n)
                            d[i+1][j+1] = d[i+1][j+1].add( d[i][j] );
                    if (j > 0)
                            d[i+1][j-1] = d[i+1][j-1].add( d[i][j] );
            }
    String ans = new String();
    if (k.compareTo( d[n*2][0] ) > 0)
            ans = "No solution";
    else {
            int depth = 0;
            for (int i=n*2-1; i>=0; --i)
                    if (depth+1 <= n && d[i][depth+1].compareTo( k ) >= 0) {
                            ans += '(';
                            ++depth;
                    }
                    else {
                            ans += ')';
                            if (depth+1 <= n)
                                    k = k.subtract( d[i][depth+1] );
                            --depth;
                    }
    }
    int n;  BigInteger k;  // входные данные
    BigInteger d[][] = new BigInteger [n*2+1][n+1];
    for (int i=0; i<=n*2; ++i)
            for (int j=0; j<=n; ++j)
                    d[i][j] = BigInteger.ZERO;
    d[0][0] = BigInteger.ONE;
    for (int i=0; i<n*2; ++i)
            for (int j=0; j<=n; ++j) {
                    if (j+1 <= n)
                            d[i+1][j+1] = d[i+1][j+1].add( d[i][j] );
                    if (j > 0)
                            d[i+1][j-1] = d[i+1][j-1].add( d[i][j] );
            }
    String ans = new String();
    int depth = 0;
    char [] stack = new char[n*2];
    int stacksz = 0;
    for (int i=n*2-1; i>=0; --i) {
            BigInteger cur;
            // '('
            if (depth+1 <= n)
                    cur = d[i][depth+1].shiftLeft( (i-depth-1)/2 );
            else
                    cur = BigInteger.ZERO;
            if (cur.compareTo( k ) >= 0) {
                    ans += '(';
                    stack[stacksz++] = '(';
                    ++depth;
                    continue;
            }
            k = k.subtract( cur );
            // ')'
            if (stacksz > 0 && stack[stacksz-1] == '(' && depth-1 >= 0)
                    cur = d[i][depth-1].shiftLeft( (i-depth+1)/2 );
            else
                    cur = BigInteger.ZERO;
            if (cur.compareTo( k ) >= 0) {
                    ans += ')';
                    --stacksz;
                    --depth;
                    continue;
            }
            k = k.subtract( cur );
            // '['
            if (depth+1 <= n)
                    cur = d[i][depth+1].shiftLeft( (i-depth-1)/2 );
            else
                    cur = BigInteger.ZERO;
            if (cur.compareTo( k ) >= 0) {
                    ans += '[';
                    stack[stacksz++] = '[';
                    ++depth;
                    continue;
            }
            k = k.subtract( cur );
            // ']'
            ans += ']';
            --stacksz;
            --depth;
    }
    int d[n+1][k+1]; // изначально заполнен нулями
    d[0][0][0] = 1;
    for (int i=1; i<=n; ++i)
            for (int j=1; j<=i && j<=k; ++j)
                    for (int s=1; s<=i; ++s)
                            d[i][j] += C[i-1][s-1] * conn[s] * d[i-s][j-1];
    System.out.println( d[n][k][n];
}

Материал разбит как 알고리즘ическая 문제: изучить постановку, понять асимптотику и реализовать 알고리즘 на выбранном языке.

Vacancies for this task

활성 채용 with overlapping task tags are 표시됨.

전체 채용
아직 활성 채용이 없습니다.