E135. Правильные скобочные последовательности
Источник: e-maxx.ru/algo, страница PDF 432.
Правильной скобочной последовательностью называется chuỗi, состоящая только из символов "скобки" (чаще всего рассматриваются только круглые скобки, но здесь будет рассматриваться и общий случай нескольких типов скобок), где каждой закрывающей скобке найдётся соответствующая открывающая (причём того же типа). Здесь мы рассмотрим классические задачи на правильные скобочные последовательности (далее для краткости просто "последовательности"): проверка на правильность, количество последовательностей, генерация всех последовательностей, нахождение лексикоđồ thịически следующей последовательности, нахождение
-
ой последовательности в отсортированном списке всех последовательностей, и, наоборот, Định nghĩa номера последовательности. Каждая из задач рассмотрена в двух случаях — когда разрешены скобки только одного типа, и когда нескольких типов.
Проверка на правильность
Пусть сначала разрешены скобки только одного типа, тогда проверить последовательность на правильность можно
очень простым Thuật toánом. Пусть
— это текущее количество открытых скобок. Изначально
. Будем двигаться по строке слева направо, если текущая скобка открывающая, то увеличим
на единицу,
иначе уменьшим. Если при этом когда-то получалось отрицательное number, или в конце работы Thuật toánа отлично от нуля, то данная chuỗi не является правильной скобочной последовательностью, иначе является. Если допустимы скобки нескольких типов, то Thuật toán нужно изменить. Вместо счётчика
следует создать стек,
в который будем класть открывающие скобки по мере поступления. Если текущий символ строки — открывающая скобка, то кладём его в стек, а если закрывающая — то проверяем, что стек не пуст, и что на его вершине лежит скобка того же типа, что и текущая, и затем достаём эту скобку из стека. Если какое-либо из условий не выполнилось, или в конце работы Thuật toánа стек остался не пуст, то последовательность не является правильной скобочной, иначе является.
Таким образом, обе эти задачи мы научились решать за время
.
Количество последовательностей
Формула
Количество правильных скобочных последовательностей с одним типом скобок можно вычислить как
number Каталана. Т.е. если есть
пар скобок (chuỗi длины
), то количество будет равно:
Пусть теперь имеется не один, а
типов скобок. Тогда каждая пара скобок независимо от остальных может
принимать один из
типов, а потому мы получаем такую формулу:
dynamic programming
С другой стороны, к этой задаче можно подойти и с точки зрения динамического
программирования. Пусть
— количество правильных скобочных последовательностей из
пар
скобок. Заметим, что в первой позиции всегда будет стоять открывающая скобка. Понятно, что внутри этой пары скобок стоит какая-то правильная скобочная последовательность; аналогично, после этой пары скобок также стоит правильная скобочная последовательность. Теперь чтобы посчитать
, переберём, сколько пар скобок
будет стоять внутри этой первой пары, тогда, соответственно,
пара скобок будет стоять после этой
первой пары. Следовательно, формула для
имеет вид:
Начальное значение для этой рекуррентной формулы — это
.
Нахождение всех последовательностей
Иногда it is required find и вывести все правильные скобочные последовательности указанной длины
(в данном
случае
— это длина строки). Для этого можно начать с лексикоđồ thịически первой последовательности
, а затем находить
каждый раз лексикоđồ thịически следующую последовательность с помощью Thuật toánа, описанного в следующем разделе.
Но если Ràng buộc не очень большие (
до
), то можно поступить значительно проще. Найдём
всевозможные перестановки этих скобок (для этого удобно использовать функцию next_permutation()), их будет
,
и каждую проверим на правильность вышеописанным Thuật toánом, и в случае правильности выведем текущую последовательность. Также процесс нахождения всех последовательностей можно оформить в виде рекурсивного перебора с отсечениями (что в идеале можно довести по скорости работы до первого Thuật toánа).
Нахождение следующей последовательности
Здесь рассматривается только случай одного типа скобок. По заданной правильной скобочной последовательности it is required find правильную скобочную последовательность, которая находится следующей в лексикоđồ thịическом порядке после текущей (или выдать "No solution", если такой не существует). Понятно, что в целом Thuật toán выглядит следующим образом: найдем такую самую правую открывающую скобку, которую мы имеем право заменить на закрывающую (так, чтобы в этом месте правильность не нарушалась), а всю оставшуюся справа строку заменим на лексикоđồ thịически минимальную: т.е. сколько-то открывающих скобок, затем все оставшиеся закрывающие скобки. Иными словами, мы пытаемся оставить без изменения как можно более длинный префикс исходной последовательности, а в суффиксе эту последовательность заменяем на лексикоđồ thịически минимальную. Осталось научиться искать эту самую позицию первого изменения. Для этого будем идти по строке справа налево
и поддерживать баланс
открытых и закрытых скобок (при встрече открывающей скобки будем
уменьшать
, а при закрывающей — увеличивать). Если в какой-то момент мы стоим на открывающей скобке, а баланс после обработки этого символа больше нуля, то мы нашли самую правую позицию, от которой мы можем
начать изменять последовательность (в самом деле,
означает, что слева имеется не закрытая ещё
скобка). Поставим в текущую позицию закрывающую скобку, затем максимально возможное количество открывающих скобок, а затем все оставшиеся закрывающие скобки, — ответ найден. Если мы просмотрели всю строку и так и не нашли подходящую позицию, то текущая последовательность — максимальна, и ответа не существует. Cài đặt Thuật toánа:
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 её номер в списке лексикоđồ thịически упорядоченных правильных скобочных последовательностей.
Научимся считать вспомогательную динамику
, где
— длина скобочной последовательности
(она "полуправильна": всякой закрывающей скобке имеется парная открывающая, но не все открытые скобки закрыты), — баланс (т.е. разность между количеством открывающих и закрывающих скобок),
— количество
таких последовательностей. При подсчёте этой динамики мы считаем, что скобки бывают только одного типа.
Считать эту динамику можно следующим образом. Пусть
— величина, которую мы хотим посчитать. Если
, то ответ понятен сразу:
, все остальные
. Пусть теперь
, тогда
мысленно переберём, чему был равен последний символ этой последовательности. Если он был равен '(', то до
этого символа мы находились в состоянии
. Если он был равен ')', то предыдущим было
состояние
. Таким образом, получаем формулу:
(считается, что все значения
при отрицательном
равны нулю). Таким образом, эту динамику мы
можем посчитать за
. Перейдём теперь к решению самой задачи. Сначала пусть допустимы только скобки одного типа. Заведём счётчик
глубины вложенности в скобки, и
будем двигаться по последовательности слева направо. Если текущий символ
(
) равен '(', то
мы увеличиваем
на 1 и переходим к следующему символу. Если же текущий символ равен ')', то мы
должны прибавить к ответу
, тем самым given, что в этой позиции мог бы
стоять символ '(' (который бы привёл к лексикоđồ thịически меньшей последовательности, чем текущая); затем
мы уменьшаем
на единицу.
Пусть теперь разрешены скобки нескольких
типов. Тогда при рассмотрении текущего символа
до
пересчёта
мы должны перебирать все скобки, которые меньше текущего символа, пробовать ставить эту скобку
в текущую позицию (получая тем самым новый баланс
), и и прибавлять к ответу
количество соответствующих "хвостов" - завершений (которые имеют длину
, баланс
и
типов скобок). Утверждается, что формула для этого количества имеет вид: Эта формула выводится из следующих соображений. Сначала мы "забываем" про то, что скобки бывают нескольких
типов, и просто берём ответ из
. Теперь посчитаем, как изменится ответ из-за наличия
типов скобок. У нас имеется
неопределённых позиций, из которых
являются
скобками, закрывающими какие-то из открытых ранее, — значит, тип таких скобок мы варьировать не можем. А вот
все остальные скобки (а их будет
пар) могут быть любого из
типов, поэтому
ответ умножается на эту степень числа
.
Нахождение
-ой последовательности
Здесь пусть
— количество пар скобок в последовательности. В данной задаче по заданному
it is required find
-
ую правильную скобочную последовательность в списке лексикоđồ thịически упорядоченных последовательностей.
Как и в предыдущем разделе, посчитаем динамику
— количество правильных
скобочных последовательностей длины
с балансом
. Пусть сначала допустимы только скобки одного типа.
Будем двигаться по символам искомой строки, с
-го по
-ый. Как и в предыдущей задаче, будем хранить
счётчик
— текущую глубину вложенности в скобки. В каждой текущей позиции будем перебирать возможный символ - открывающую скобку или закрывающую. Пусть мы хотим поставить сюда открывающую скобку,
тогда мы должны посмотреть на значение
. Если оно
, то мы ставим в текущую
позицию открывающую скобку, увеличиваем
на единицу и переходим к следующему символу. Иначе мы
отнимаем от
величину
, ставим закрывающую скобку и уменьшаем значение
. В
конце концов мы и получим искомую скобочную последовательность. Cài đặt на языке Java с использованием длинной арифметики:
int n; BigInteger k; // Đầu vàoные данные
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;
} }
Пусть теперь разрешён не один, а
типов скобок. Тогда Thuật toán решения будет отличаться от предыдущего
случая только тем, что мы должны домножать значение
на величину
, чтобы учесть, что в этом остатке могли быть скобки различных типов, а парных скобок в
этом остатке будет только
, поскольку
скобок являются закрывающими
для открывающих скобок, находящихся вне этого остатка (а потому их типы мы варьировать не можем). Cài đặt на языке Java для случая двух типов скобок - круглых и квадратных:
int n; BigInteger k; // Đầu vàoные данные
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;
}
Количество помеченных đồ thịов
given number
вершин. it is required посчитать количество
различных помеченных đồ thịов с
vertexми (т.
е. вершины đồ thịа помечены различными числами от
до
, и đồ thịы сравниваются с учётом этой покраски
вершин). Рёбра đồ thịа неориентированы, петли и кратные рёбра запрещены. Рассмотрим множество всех возможных рёбер đồ thịа. Для любого ребра
положим, что
(основываясь
на неориентированности đồ thịа и отсутствии петель). Тогда множество всех возможных рёбер đồ thịа имеет мощность , т.е. . Поскольку любой помеченный đồ thị однозначно определяется своими рёбрами, то количество помеченных đồ thịов с vertexми равно:
Количество связных помеченных đồ thịов
По сравнению с предыдущей задачей мы дополнительно накладываем ограничение, что đồ thị должен быть связным.
Обозначим искомое number через
. Научимся, наоборот, считать количество несвязных đồ thịов; тогда количество связных đồ thịов получится как минус найденное number. Более того, научимся считать количество корневых (т.е. с выделенной вершиной - корнем) несвязных đồ thịов; тогда количество несвязных đồ thịов будет получаться из него делением на . Заметим, что, так как đồ thị несвязный, то в нём найдётся компонента связности, внутри которой лежит корень, а остальной đồ thị будет представлять собой ещё несколько (как минимум одну) компонент связности.
Переберём количество
вершин в этой компоненте связности, содержащей корень (очевидно,
),
и найдём количество таких đồ thịов. Во-первых, мы должны выбрать
вершин из
, т.е. ответ умножается на
.
Во-вторых, компонента связности с корнем даёт множитель
. В-третьих, оставшийся đồ thị из
вершин является произвольным đồ thịом, а потому он даёт множитель
. Наконец, количество способов
выделить корень в компоненте связности из
вершин равно
. Итого, при фиксированном
количество
корневых несвязных đồ thịов равно:
Значит, количество несвязных đồ thịов с
vertexми равно: Наконец, искомое количество связных đồ thịов равно:
Количество помеченных đồ thịов с
компонентами связности
Основываясь на предыдущей формуле, научимся считать количество помеченных đồ thịов с
vertexми и
компонентами связности. Сделать это можно с помощью динамического программирования. Научимся считать
—
количество помеченных đồ thịов с
vertexми и
компонентами связности.
Научимся вычислять очередной element
, зная предыдущие значения. Воспользуемся стандартным
приёмом при решении таких задач: возьмём вершину с номером 1, она принадлежит какой-то компоненте, вот
эту компоненту мы и будем перебирать. Переберём размер
этой компоненты, тогда количество способов выбрать
такое множество вершин равно
(одну вершину — вершину 1 — перебирать не надо). Количество же
способов построить компоненту связности из
вершин мы уже умеем считать — это
. После удаления
этой компоненты из đồ thịа у нас остаётся đồ thị с
vertexми и
компонентами связности, т.е. мы получили рекуррентную зависимость, по которой можно вычислять значения : Итого получаем Ví dụно такой код:
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# lời giải
bản nháp tự động, xem lại trước khi gửiusing 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++ lời giải
đã khớp/gốcstring 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 lời giải
bản nháp tự động, xem lại trước khi gửiimport 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];
}
Материал разбит как Thuật toánическая Bài toán: изучить постановку, понять асимптотику и реализовать Thuật toán на выбранном языке.
Vacancies for this task
việc làm đang hoạt động with overlapping task tags are đã hiển thị.