Біноміальний коефіцієнт
Сполученням з елементів по називається набір з елементів, вибраних із заданих елементів. При цьому набори, які відрізняються тільки порядком слідування елементів (але не складом), вважаються однаковими. Саме завдяки цій властивості сполучення відрізняються від розміщень.
Наприклад, розглянемо множину з елементів: . Набори і є однаковими як сполучення (хоча різні як розміщення), оскільки складені з тих самих елементів .
Формула для обчислення кількості сполучень з по дорівнює
Числа називаються біноміальними коефіцієнтами.
Коли ми вибираємо нуль елементів із множини з елементів, ми фактично робимо “пустий” вибір. Порожня множина, в цьому контексті, вважається допустимим сполученням. Уявіть, що у вас є коробка з кулями, і ви хочете вибрати з них. Навіть якщо ви нічого не вибираєте, це все одно є допустимим варіантом вибору. Таким чином, означає кількість способів вибрати елементів з , що дорівнює .
Ми можемо розглядати це як один єдиний спосіб не вибирати жоден елемент з множини, що і дає результат .
Коли ми вибираємо один елемент із множини з елементів, у нас є варіантів вибору: ми можемо вибрати будь-який з елементів. Наприклад, якщо у нас є множина з чисел від до (), то ми можемо вибрати одне число з цієї множини, і у нас є варіантів для цього вибору.
Значення дорівнює , тому що є способів вибрати один елемент з множини з елементів.
При виборі елементів із — елементної множини, ми формуємо комбінації пар. Для побудови такої пари ми спочатку вибираємо один елемент, а потім другий.
Перший елемент можна вибрати з елементів.
Після вибору першого елемента, у нас залишається елемент для вибору другого елемента (оскільки ми не можемо вибрати повторно той самий елемент).
Таким чином, загальна кількість комбінацій пар елементів, які можна вибрати з — елементної множини, дорівнює добутку числа способів вибрати перший і другий елементи: .
Проте, кожна пара врахована двічі, оскільки порядок елементів у парі не має значення. Наприклад, і вважаються однією і тією ж парою. Щоб врахувати це, слід поділити загальну кількість комбінацій на . Таким чином, дорівнює .
Властивість симетрії: .
Кількість способів вибрати елементів з дорівнює кількості способів не вибрати елементів з . Але не вибрати елементів з ми можемо способами. Відповідно .
Вправа. Обчислити значення наступних біноміальних коефіцієнтів:
1) | 3) | 5) |
2) | 4) | 6) |
Формула додавання: .
Доказ. Обчислимо значення правої частини рівняння:
Для обчислення біноміального коефіцієнта можна скористатися наступним співвідношенням:
Оголосимо змінну і ініціалізуємо її значенням . Потім помножимо на і розділимо результат на . Після цього помножимо на і розділимо на . Процес множень і ділення будемо продовжувати разів (чисельник і знаменник після скорочення на містить множників).
Функція Cnk обчислює значення біноміального коефіцієнта ітеративним методом.
int Cnk(int n, int k) { int res = 1; for(int i = 1; i <= k; i++) res = res * (n - i + 1) / i; return res; }
Основна частина програми. Читаємо вхідні дані.
scanf("%d %d",&n,&k);
Обчислюємо і виводимо відповідь.
res = Cnk(n,k); printf("%d\n",res);
Для рекурсивної реалізації біноміального коефіцієнта можна скористатися співвідношенням:
Функція Cnk обчислює значення біноміального коефіцієнта рекурсивним методом.
int Cnk(int n, int k) { if (n == k) return 1; if (k == 0) return 1; return Cnk(n - 1, k - 1) + Cnk(n - 1, k); }
Основна частина програми. Читаємо вхідні дані.
scanf("%d %d",&n,&k);
Обчислюємо і виводимо відповідь.
res = Cnk(n,k); printf("%d\n",res);
Скількома способами серед ЛКШат можна вибрати таких, яким дістанеться кефір? Відповідь виведіть за модулем .
Вхідні дані. Цілі числа і .
Вихідні дані. Виведіть шукану кількість способів за модулем .
6 3
20
Відповіддю до задачі буде значення . Оскільки слід знайти біноміальний коефіцієнт за модулем, постараємось при обчисленнях уникнути ділення. Для цього скористаємося співвідношенням .
Оскільки , то використовуємо техніку мемоізації.
Оголосимо константи і масив , для якого .
#define MAX 510 #define MOD 9929 int cnk[MAX][MAX];
Функція c обчислює значення біноміального коефіцієнта .
int c(int n, int k) { if (cnk[n][k] > 0) return cnk[n][k]; if (n - k < k) return c(n,n-k); if (!k) return cnk[n][k] = 1; return cnk[n][k] = (c(n-1,k) + c(n-1,k-1)) % MOD; }
Основна частина програми. Ініціалізуємо масив нуля
ми. Читаємо вхідні дані.
memset(cnk,0,sizeof(cnk)); scanf("%d %d",&n,&k);
Обчислюємо і виводимо відповідь.
printf("%d\n",c(n,k));
Біноміальні коефіцієнти з'являються в біномі Ньютона:
Розглянемо приклади:
За заданим невід'ємним цілим числом знайдіть суму біноміальних коефіцієнтів
Вхідні дані. Одне невід'ємне ціле число .
Вихідні дані. Виведіть значення суми.
2
4
Формула бінома Ньютона має вигляд:
Якщо покласти , то дане співвідношення приймає такий вигляд:
або
Таким чином, вказана сума дорівнює .
Приклад
При :
При :
При :
Читаємо вхідне значення .
scanf("%lld", &n);
Обчислюємо і виводимо відповідь — значення .
res = 1LL << n; printf("%lld\n", res);
За заданим невід'ємним цілим числом знайдіть суму біноміальних коефіцієнтів
Вхідні дані. Одне невід'ємне ціле число .
Вихідні дані. Виведіть значення суми.
3
20
Розглянемо об'єктів і знайдемо кількість способів вибрати об'єктів з .
Розіб'ємо множину на дві частини: . Щоб вибрати об'єктів з , нам потрібно вибрати об'єктів з лівої половини (тобто з множини і об'єктів з правої (з множини . Кількість способів зробити такий вибір відповідно до правила множення дорівнює . Оскільки може приймати значення від до , то дорівнює кількості способів вибрати об'єктів з , тобто . Таким чином
Приклад
Для відповідь дорівнює
Водночас .
Функція Cnk обчислює значення біноміального коефіцієнта .
long long Cnk(long long n, long long k) { long long res = 1; if (k > n - k) k = n - k; for (long long i = 1; i <= k; i++) res = res * (n - i + 1) / i; return res; }
Основна частина програми. Читаємо вхідне значення .
scanf("%lld", &n);
Обчислюємо і виводимо відповідь.
res = Cnk(2*n, n); printf("%lld\n", res);
За заданими значеннями і знайдіть кількість натуральних розв'язків рівняння
Вхідні дані. Два натуральні числа і .
Вихідні дані. Виведіть кількість натуральних розв'язків для заданого рівняння. Відомо, що відповідь не перевищує .
3 4
3
Розглянемо послідовність із одиниць: . Між будь-якими двома одиницями можна вставити знак ‘’. Наприклад . Такий запис буде позначати суму , де кожен доданок – кількість одиниць, що стоять поряд. Кількість позицій, куди можна вставити знак ‘’, дорівнює . Оскільки необхідно отримати суму з доданків, то слід вставити знак ‘’.
Нам слід вставити плюс на місце. Це можна зробити способами.
Приклад
Розглянемо рівняння . Воно має позитивних цілих розв'язки:
Наприклад, одиниці на доданків можна розбити способами:
Функція Cnk обчислює значення біноміального коефіцієнта .
long long Cnk(long long k, long long n) { long long res = 1; if (k > n - k) k = n - k; for (long long i = 1; i <= k; i++) res = res * (n - i + 1) / i; return res; }
Основна частина програми. Читаємо вхідні дані.
scanf("%lld %lld", &k, &n);
Обчислюємо і виводимо відповідь .
res = Cnk(k - 1, n - 1); printf("%lld\n", res);
За заданими значеннями і знайдіть кількість невід'ємних цілих розв'язків рівняння
Вхідні дані. Два натуральні числа і .
Вихідні дані. Виведіть кількість невід'ємних цілих розв'язків для заданого рівняння. Відомо, що відповідь не перевищує .
3 4
15
Розглянемо послідовність із позицій. У позиціях слід поставити . У позицію слід поставити символ '' (щоб отримати доданків).
Будь-якому розташуванню одиниць і знаків '' по позиціях буде відповідати деякий розв'язок заданого рівняння. Наприклад, розглянемо деякі розв'язки рівняння одиниці і плюси):
Нам слід вставити плюс на позицій. Це можна зробити способами.
Приклад
Розглянемо рівняння . Його розв'язками будуть:
і її перестановки
і її перестановок
і її перестановки
і її перестановки
Усього є розв'язків.
Функція Cnk обчислює значення біноміального коефіцієнта .
long long Cnk(long long k, long long n) { long long res = 1; if (k > n - k) k = n - k; for (long long i = 1; i <= k; i++) res = res * (n - i + 1) / i; return res; }
Основна частина програми. Читаємо вхідні дані.
scanf("%lld %lld", &k, &n);
Обчислюємо і виводимо відповідь .
res = Cnk(k - 1, n + k - 1); printf("%lld\n", res);
Нехай — ціле невід'ємне число. Позначимо
Для заданих і обчисліть значення .
Вхідні дані. Перша строка містить кількість тестів . Кожна з наступних строк містить два цілих числа і .
Вихідні дані. Виведіть строк, кожна з яких містить значення для відповідного тесту.
6 0 0 1 0 1 1 2 0 2 1 2 2
1 1 1 1 2 1
Обчислення будемо проводити з використанням -розрядних беззнакових цілих чисел (unsigned long long). Очевидно, що
Присвоїмо змінній значення , після чого будемо її множити на для всіх від до . Кожного разу ділення на буде цілочисельним, проте при множенні можна отримати переповнення. Нехай . Тоді операцію
перепишемо через
При такій реалізації переповнення при множенні не відбудеться (відповідь є -розрядним беззнаковим цілим числом). Зазначимо, що спочатку слід виконати ділення , а потім множення на отримане частке.
Для обчислення слід виконати ітерацій. Але що робити, якщо слід обчислити ? Відповідь не перевищить , тому такі вхідні дані можливі. Оскільки , то при слід покласти .
Приклад
Розглянемо наступний приклад:
Нехай , і залишилось виконати множення . Обчислимо . Тому .
Функція gcd обчислює найбільший спільний дільник чисел і .
unsigned long long gcd(unsigned long long a, unsigned long long b) { return (!b) ? a : gcd(b,a % b); }
Функція Cnk обчислює значення біноміального коефіцієнта .
unsigned long long Cnk(unsigned long long n, unsigned long long k) { unsigned long long CnkRes = 1, t, i; if (k > n - k) k = n - k; for(i = 1; i <= k; i++) { t = gcd(CnkRes, i); CnkRes = (CnkRes / t) * ((n - i + 1) / (i / t)); } return CnkRes; }
Основна частина програми. Читаємо вхідні дані. Послідовно обробляємо тести.
scanf("%d",&t); while(t--) { scanf("%llu %llu",&n,&k); res = Cnk(n,k); printf("%llu\n",res); }
Дано натуральне число . Знайдіть значення наступної суми
Вхідні дані. Одне натуральне число .
Вихідні дані. Виведіть значення шуканої суми.
3
20
Перепишемо задану суму у вигляді:
Сума у дужках дорівнює . Якщо у формулі бінома Ньютона
покласти , то отримаємо співвідношення: , або
Обчислимо залишок суми:
Таким чином, відповідь дорівнює .
Приклад
Обчислимо відповідь для . При безпосередньому обчисленні:
При обчисленні за формулою: .
Читаємо вхідне значення .
scanf("%lld", &n);
Обчислюємо і виводимо відповідь.
res = (1LL << (n - 1)) * (n + 2); printf("%lld\n", res);
Реалізація алгоритму – функція
Оголосимо масив для запам'ятовування результатів: .
int dp[31][31];
Функція Cnk обчислює значення біноміального коефіцієнта .
int Cnk(int n, int k) { if (n == k) return 1; if (k == 0) return 1; if (dp[n][k] != -1) return dp[n][k]; return dp[n][k] = (Cnk(n - 1, k - 1) + Cnk(n - 1, k)) % 9929; }
Основна частина програми. Читаємо вхідне значення .
scanf("%d", &n); memset(dp, -1, sizeof(dp));
Обчислюємо зазначену суму.
res = 0; for (i = 0; i <= n; i++) res += (i + 1) * Cnk(n, i);
Виводимо відповідь.
printf("%d\n", res);
У вас є аркуш паперу, і ви обираєте на ньому прямокутник розміром . Назвемо цей прямокутник разом з лініями, на яких він знаходиться, сіткою. Починаючи з лівого нижнього кута сітки, ви переміщаєте олівець до правого верхнього кута, стежачи за тим, щоб він залишався на лініях і рухався тільки вправо або вгору. Результат показаний зліва:
Дійсно шедевр, чи не так? Повторюючи процедуру ще раз, ви отримаєте зображення, показане праворуч. Тепер виникає питання: скільки різних витворів мистецтва можна таким чином створити?
Вхідні дані. Два натуральні числа і .
Вихідні дані. Виведіть кількість різних витворів мистецтва, які можна створити, використовуючи описану вище процедуру. Можна стверджувати, що це число відповідає — бітовому знаковому цілому числу.
3 4
35
1 1
2
Шуканий шлях являє собою ламану, що складається з ланок. З них ланок повинні бути вертикальними, а решта — горизонтальними. Кількість варіантів вибрати вертикальних ланок з дорівнює .
Приклад
Для першого прикладу . Відповідь дорівнює .
Для другого прикладу . Відповідь дорівнює .
Функція Cnk обчислює значення біноміального коефіцієнта .
long long Cnk(long long n, long long k) { long long res = 1; if (k > n - k) k = n - k; for (long long i = 1; i <= k; i++) res = res * (n - i + 1) / i; return res; }
Основна частина програми. Читаємо вхідні дані.
scanf("%lld %lld", &n, &m);
Обчислюємо і виводимо відповідь .
res = Cnk(n + m, n); printf("%lld\n", res);
Гуннар — досить старий і забудькуватий дослідник. Зараз він пише статтю про безпеку в соціальних мережах, яка включає в себе деякі елементи комбінаторики. Він написав програму для обчислення біноміальних коефіцієнтів, щоб перевірити деякі розрахунки.
Біноміальний коефіцієнт — це число
де і — невід'ємні числа.
Гуннар використовує свою програму для обчислення і отримав число як результат. На жаль, оскільки він забудькуватий, він забув числа і , які він використовував як вхідні дані. Ці два числа були результатом довгих обчислень, і вони були записані на одному з численних аркушів, що лежать на його столі. Замість того щоб шукати в паперах, він вирішив відновити числа і за отриманою відповіддю. Чи можете ви допомогти йому знайти всі такі можливі значення?
Вхідні дані. Перша строка містить кількість тестів, не більше . Кожен тест задається в одній строкі і містить ціле число — результат програми Гуннара.
Вихідні дані. Для кожного тесту виведіть дві строки. Перша строка повинна містити кількість способів виразити за допомогою біноміального коефіцієнта. У другій строкі повинні розташовуватись всі пари , що задовольняють . Пари слід розташувати за зростанням , а у випадку рівності за зростанням . Формат виводу пар наведено в прикладі.
2 2 15
1 (2,1) 4 (6,2) (6,4) (15,1) (15,14)
Якщо , то . Достатньо знайти розв'язок і разом з парою вивести також пару . При ці дві пари співпадають.
Нехай — найменше число, для якого . Тоді очевидно що .
Зафіксуємо таке що і розглянемо функцію . Тоді при функція є монотонно зростаючою. Відповідно можна розв'язати рівняння бінарним пошуком.
Таким чином для розв'язання задачі слід перебрати значення і для кожного такого розв'язати рівняння щодо бінарним пошуком. Знайдене значення повинно бути цілочисельним.
Приклад
Розглянемо рівняння . Зважаючи на те що , а , то нам достатньо перебрати .
Нехай , розглянемо рівняння або , . Бінарним пошуком в проміжку знаходимо цілочисельне розв'язання . Зважаючи на те що , маємо два розв'язки: .
Шукані пари будемо зберігати у векторі пар .
vector<pair<long long,long long> > res;
Функція Cnk обчислює значення біноміального коефіцієнта .
long long Cnk(long long n, long long k) { long long i, Res = 1; if (k > n - k) k = n - k; for(i = 1; i <= k; i++) {
Якщо на черговій ітерації результат перевищує (ми шукаємо розв'язок рівняння ), то зупиняємось. Таким виходом з функції ми також уникнемо переповнення.
if (1.0 * Res * (n - i + 1) / i > m) return m + 1; Res = Res * (n - i + 1) / i; } return Res; }
Основна частина програми. Читаємо вхідні дані.
scanf("%d",&tests); while (tests--) { res.clear(); scanf("%lld",&m);
Перебираємо значення від до тих пір поки .
for(k = 0; Cnk(2*k,k) <= m; k++) {
Шукаємо значення бінарним пошуком.
long long lo = 2*k, hi = m; while (lo < hi) { long long n = (lo + hi) / 2; if (Cnk(n,k) < m) lo = n + 1; else hi = n; }
Якщо , то розв'язок знайдено. Заносимо в результат одну або дві пари розв'язків.
if (Cnk(lo,k) == m) { res.push_back(make_pair(lo,k)); if (lo != 2*k) res.push_back(make_pair(lo,lo - k)); } }
Сортуємо пари.
sort(res.begin(),res.end());
Виводимо відповідь — кількість знайдених пар і самі пари.
printf("%d\n",res.size()); for(i = 0; i < res.size(); i++) printf("(%lld,%lld) ", res[i].first,res[i].second); printf("\n"); }
Асимптотичні оцінки для біноміального коефіцієнта
Теорема 1. Між біноміальними коефіцієнтами має місце співвідношення:
Теорема 2. . Слід із того, що
Теорема 3. . Слід із того, що , а самих коефіцієнтів . Тому більший з коефіцієнтів буде більшим .
Теорема 4. Формула Стірлінга. .
Більш точне наближення має вигляд:
Теорема 5.
Теорема 6.