Числа Каталана
Числа Каталана — це числова послідовність, яка виявляється корисною в багатьох комбінаторних задачах, часто пов'язаних з рекурсивно визначеними об'єктами.
Цю послідовність назвали на честь бельгійського математика Каталана, який жив у 19 столітті. (Насправді ж вона була відома ще раніше Ейлеру, який жив на століття раніше за Каталана).
Перші кілька чисел Каталана (починаючи з нуля):
- Чи відповіді на малих дають послідовність ? Тоді це майже напевно числа Каталана.
- Чи задача про підрахунок збалансованих рекурсивних структур — правильних дужкових послідовностей, бінарних дерев, тріангуляцій многокутника, неперетинних хорд/розбиттів?
- Чи треба лише порахувати кількість, а не згенерувати самі об'єкти? (якщо ні → Генерування комбінацій або Дужкові послідовності)
Застосування в деяких комбінаторних задачах
Число Каталана є розв'язком для:
- Кількість правильних дужкових послідовностей, що складаються з відкривних і закривних дужок.
- Кількість кореневих повних двійкових дерев із листком (вершини не нумеровані). Кореневе двійкове дерево називається повним, якщо кожна вершина має або двох нащадків, або жодного.
- Кількість способів повністю розставити дужки навколо множників.
- Кількість тріангуляцій опуклого многокутника з сторонами (тобто кількість розбиттів многокутника на неперетинні трикутники за допомогою діагоналей).
- Кількість способів з'єднати точок на колі так, щоб утворити неперетинних хорд.
- Кількість неізоморфних повних двійкових дерев із внутрішніми вузлами (тобто вузлами, які мають хоча б одного нащадка).
- Кількість монотонних шляхів на ґратці з точки до точки у квадратній ґратці розміру , які не проходять вище головної діагоналі (тобто тієї, що з'єднує з ).
- Кількість перестановок довжини , які можна відсортувати стеком (тобто можна показати, що перестановку можна відсортувати стеком тоді й лише тоді, коли не існує таких індексів , що ).
- Кількість неперетинних розбиттів множини з елементів.
- Кількість способів покрити драбинку за допомогою прямокутників (драбинка складається з стовпців, де стовпець має висоту ).
Обчислення
Існують дві формули для чисел Каталана: рекурсивна та аналітична. Оскільки ми вважаємо, що всі згадані вище задачі еквівалентні (мають той самий розв'язок), то для доведення наведених нижче формул ми обиратимемо ту задачу, з якою це зробити найпростіше.
Рекурсивна формула
Рекурентну формулу легко вивести із задачі про правильну дужкову послідовність.
Найлівіша відкривна дужка відповідає певній закривній дужці , яка ділить послідовність на 2 частини, кожна з яких, своєю чергою, теж має бути правильною дужковою послідовністю. Таким чином, формула теж ділиться на 2 частини. Якщо ми позначимо , то для фіксованого існуватиме рівно таких дужкових послідовностей. Підсумувавши це за всіма допустимими , ми отримуємо рекурентне співвідношення для .
Можна міркувати й так. За означенням позначає кількість правильних дужкових послідовностей. Тепер послідовність можна поділити на 2 частини довжини і , кожна з яких має бути правильною дужковою послідовністю. Приклад:
можна поділити на та , але не можна поділити на та . Знову ж таки, підсумувавши за всіма допустимими , ми отримуємо рекурентне співвідношення для .
Реалізація на C++
- C++
- Python
- TypeScript
- Go
const int MOD = ....
const int MAX = ....
int catalan[MAX];
void init() {
catalan[0] = catalan[1] = 1;
for (int i=2; i<=n; i++) {
catalan[i] = 0;
for (int j=0; j < i; j++) {
catalan[i] += (catalan[j] * catalan[i-j-1]) % MOD;
if (catalan[i] >= MOD) {
catalan[i] -= MOD;
}
}
}
}
MOD = ... # модуль
MAX = ... # верхня межа
catalan = [0] * MAX
def init():
catalan[0] = catalan[1] = 1
for i in range(2, n + 1):
catalan[i] = 0
for j in range(i):
catalan[i] += (catalan[j] * catalan[i - j - 1]) % MOD
if catalan[i] >= MOD:
catalan[i] -= MOD
const MOD = ...; // модуль
const MAX = ...; // верхня межа
const catalan: number[] = new Array(MAX).fill(0);
function init(): void {
catalan[0] = catalan[1] = 1;
for (let i = 2; i <= n; i++) {
catalan[i] = 0;
for (let j = 0; j < i; j++) {
catalan[i] += (catalan[j] * catalan[i - j - 1]) % MOD;
if (catalan[i] >= MOD) {
catalan[i] -= MOD;
}
}
}
}
const MOD = ... // модуль
const MAX = ... // верхня межа
var catalan [MAX]int
func init() {
catalan[0], catalan[1] = 1, 1
for i := 2; i <= n; i++ {
catalan[i] = 0
for j := 0; j < i; j++ {
catalan[i] += (catalan[j] * catalan[i-j-1]) % MOD
if catalan[i] >= MOD {
catalan[i] -= MOD
}
}
}
}
Аналітична формула
(тут позначає звичайний біноміальний коефіцієнт, тобто кількість способів вибрати об'єктів із множини з об'єктів).
Наведену вище формулу легко вивести із задачі про монотонні шляхи у квадратній ґратці. Загальна кількість монотонних шляхів у ґратці розміру дорівнює .
Тепер порахуємо кількість монотонних шляхів, які перетинають головну діагональ. Розглянемо такий шлях, що перетинає головну діагональ, і знайдемо в ньому перше ребро, яке лежить вище діагоналі. Відобразимо шлях відносно діагоналі на всій його частині після цього ребра. Результатом завжди буде монотонний шлях у ґратці . З іншого боку, будь-який монотонний шлях у ґратці обов'язково перетинає діагональ. Отже, ми перелічили всі монотонні шляхи, які перетинають головну діагональ у ґратці .
Кількість монотонних шляхів у ґратці дорівнює . Назвемо такі шляхи «поганими». Як наслідок, щоб отримати кількість монотонних шляхів, які не перетинають головну діагональ, ми віднімаємо наведені вище «погані» шляхи, отримуючи формулу:
Джерела
Задачі для практики
- Codechef - PANSTACK
- Spoj - Skyline
- UVA - Safe Salutations
- Codeforces - How many trees?
- SPOJ - FUNPROB