Принцип включення-виключення
Принцип включення-виключення — це важливий комбінаторний спосіб обчислити розмір множини або ймовірність складних подій. Він пов'язує розміри окремих множин із розміром їхнього об'єднання.
- Чи треба порахувати об'єкти, що задовольняють хоча б одну (або жодну) з умов, причому умови перетинаються й пряме сумування дає переоблік?
- Чи легко рахувати перетини умов (скільки об'єктів задовольняє фіксований набір умов одночасно), хоча об'єднання — важко?
- Чи кількість умов невелика (приблизно ), щоб перебрати всі підмножин? (якщо умов незалежні підрахунки без перетинів → Біноміальні коефіцієнти)
Формулювання
Словесне формулювання
Принцип включення-виключення можна сформулювати так:
Щоб обчислити розмір об'єднання кількох множин, треба підсумувати розміри цих множин окремо, потім відняти розміри всіх попарних перетинів множин, далі додати назад розмір перетинів трійок множин, відняти розмір четвірок множин, і так далі, аж до перетину всіх множин.
Формулювання мовою теорії множин
Наведене вище означення можна записати математично так:
А у більш компактному вигляді:
Формулювання через діаграми Венна
Нехай на діаграмі зображено три множини , та :

Тоді площа їхнього об'єднання дорівнює сумі площ , та за вирахуванням двічі покритих площ , , , але з додаванням площі, покритої трьома множинами :
Це також можна узагальнити на об'єднання множин.
Формулювання мовою теорії ймовірностей
Якщо — це події, а — імовірність настання події , то ймовірність їхнього об'єднання (тобто ймовірність того, що настане хоча б одна з подій) дорівнює:
А у більш компактному вигляді:
Доведення
Для доведення зручно скористатися математичним формулюванням мовою теорії множин:
Ми хочемо довести, що будь-який елемент, який міститься хоча б в одній із множин , буде врахований у формулі рівно один раз (зауважимо, що елементи, відсутні в усіх множинах , ніколи не враховуються у правій частині формули).
Розглянемо елемент , який зустрічається у множинах . Ми покажемо, що його враховано рівно один раз. Зауважимо, що:
- у доданках, де , елемент буде враховано разів;
- у доданках, де , елемент буде враховано разів — оскільки він враховується в тих доданках, що включають дві з множин, які містять ;
- у доданках, де , елемент буде враховано разів;
- у доданках, де , елемент буде враховано разів;
- у доданках, де , елемент буде враховано нуль разів;
Це приводить нас до такої суми біноміальних коефіцієнтів:
Цей вираз дуже схожий на біноміальний розклад :
При вираз дуже схожий на . Однак він містить додатковий доданок , і весь вираз помножений на . Це приводить нас до . Отже, , що й треба було довести. Елемент враховано рівно один раз.
Узагальнення для обчислення кількості елементів рівно у множинах
Принцип включення-виключення можна переписати, щоб обчислити кількість елементів, присутніх у нуль множинах:
Розглянемо його узагальнення для обчислення кількості елементів, присутніх рівно у множинах:
Щоб довести цю формулу, розглянемо якесь конкретне . За базовим принципом включення-виключення про нього можна сказати, що:
Множини у лівій частині не перетинаються для різних , тому ми можемо підсумувати їх безпосередньо. Також варто зауважити, що будь-яка множина завжди матиме коефіцієнт , якщо вона зустрічається, і вона зустрінеться рівно для множин .
Застосування під час розв'язування задач
Принцип включення-виключення важко зрозуміти, не вивчивши його застосувань.
Спершу ми розглянемо три найпростіші задачі «на папері», що ілюструють застосування принципу, а потім перейдемо до практичніших задач, які важко розв'язати без принципу включення-виключення.
Задачі, де треба «знайти кількість способів», варті уваги, бо вони іноді приводять до поліноміальних розв'язків, а не обов'язково експоненціальних.
Проста задача про перестановки
Задача: підрахувати, скільки існує перестановок чисел від до таких, що перший елемент більший за , а останній — менший за .
Підрахуймо кількість «поганих» перестановок, тобто таких, у яких перший елемент та/або останній .
Позначимо через множину перестановок, у яких перший елемент , а через — множину перестановок, у яких останній елемент . Тоді кількість «поганих» перестановок за формулою включення-виключення дорівнюватиме:
Після простого комбінаторного обчислення отримаємо:
Залишається лише відняти це число від загальної кількості , щоб отримати кількість «хороших» перестановок.
Проста задача про послідовності з (0, 1, 2)
Задача: підрахувати, скільки існує послідовностей довжини , які складаються лише з чисел , таких, що кожне число зустрічається хоча б один раз.
Знову звернімося до оберненої задачі, тобто обчислимо кількість послідовностей, які не містять хоча б одного з чисел.
Позначимо через множину послідовностей, у яких цифра не зустрічається. Формула включення-виключення для кількості «поганих» послідовностей буде такою:
- Розмір кожної дорівнює , оскільки кожна послідовність може містити лише дві з цифр.
- Розмір кожного попарного перетину дорівнює , оскільки для побудови послідовності залишиться лише одна цифра.
- Розмір перетину всіх трьох множин дорівнює , оскільки для побудови послідовності не залишиться жодної цифри.
Оскільки ми розв'язали обернену задачу, віднімаємо її від загальної кількості послідовностей:
Кількість цілочисельних сум з верхньою межею
Розглянемо таке рівняння:
де .
Задача: підрахувати кількість розв'язків рівняння.
Забудьмо на мить про обмеження на і просто підрахуймо кількість невід'ємних розв'язків цього рівняння. Це легко зробити за допомогою методу «зірочки та перегородки»: ми хочемо розбити послідовність із одиниць на груп, що те саме, що розташувати перегородок і зірочок:
Тепер обчислимо кількість «поганих» розв'язків за принципом включення-виключення. «Поганими» будуть ті розв'язки, у яких один або більше більші або рівні .
Позначимо через множину розв'язків, де , а всі інші (вони можуть бути або ні). Щоб обчислити розмір , зауважимо, що ми, по суті, маємо ту саму комбінаторну задачу, яку розв'язали у двох абзацах вище, але тепер одиниць виключено зі слотів і вони точно належать першій групі. Отже:
Аналогічно, розмір перетину двох множин та (для ) дорівнює:
Розмір кожного перетину трьох множин дорівнює нулю, оскільки одиниць не вистачить для трьох або більше змінних, більших або рівних .
Об'єднавши все це у формулу включення-виключення і враховуючи, що ми розв'язали обернену задачу, остаточно отримуємо відповідь:
Це легко узагальнюється на чисел, що дають у сумі , з обмеженням :
Як і вище, ми вважаємо біноміальні коефіцієнти з від'ємним верхнім індексом нульовими.
Зауважимо, що цю задачу можна було б розв'язати також динамічним програмуванням або твірними функціями. Відповідь за принципом включення-виключення обчислюється за часу (припускаючи, що математичні операції на кшталт біноміального коефіцієнта виконуються за сталий час), тоді як простий підхід із ДП потребував би часу.
Кількість взаємно простих чисел на заданому проміжку
Задача: дано два числа та ; підрахувати кількість цілих чисел на проміжку , які є взаємно простими з (їхній найбільший спільний дільник дорівнює ).
Розв'яжемо обернену задачу — обчислимо кількість чисел, що не є взаємно простими з .
Позначимо прості множники числа як .
Скільки чисел на проміжку діляться на ? Відповідь на це запитання:
Однак, якщо ми просто підсумуємо ці числа, деякі числа буде враховано кілька разів (ті, що мають кілька серед своїх множників). Тому необхідно скористатися принципом включення-виключення.
Ми переберемо всі підмножин чисел , обчислимо їхній добуток і додамо або віднімемо кількість кратних цього добутку.
Ось реалізація:
- C++
- Python
- TypeScript
- Go
int solve (int n, int r) {
vector<int> p;
for (int i=2; i*i<=n; ++i)
if (n % i == 0) {
p.push_back (i);
while (n % i == 0)
n /= i;
}
if (n > 1)
p.push_back (n);
int sum = 0;
for (int msk=1; msk<(1<<p.size()); ++msk) {
int mult = 1,
bits = 0;
for (int i=0; i<(int)p.size(); ++i)
if (msk & (1<<i)) {
++bits;
mult *= p[i];
}
int cur = r / mult;
if (bits % 2 == 1)
sum += cur;
else
sum -= cur;
}
return r - sum;
}
def solve(n: int, r: int) -> int:
# Знаходимо різні прості множники числа n
p = []
i = 2
while i * i <= n:
if n % i == 0:
p.append(i)
while n % i == 0:
n //= i
i += 1
if n > 1:
p.append(n)
total = 0
# Перебираємо всі непорожні підмножини множників
for msk in range(1, 1 << len(p)):
mult = 1
bits = 0
for i in range(len(p)):
if msk & (1 << i):
bits += 1
mult *= p[i]
cur = r // mult
# Непарна кількість множників — додаємо, парна — віднімаємо
if bits % 2 == 1:
total += cur
else:
total -= cur
return r - total
function solve(n: number, r: number): number {
// Знаходимо різні прості множники числа n
const p: number[] = [];
for (let i = 2; i * i <= n; ++i) {
if (n % i === 0) {
p.push(i);
while (n % i === 0) {
n = Math.floor(n / i);
}
}
}
if (n > 1) {
p.push(n);
}
let sum = 0;
// Перебираємо всі непорожні підмножини множників
for (let msk = 1; msk < (1 << p.length); ++msk) {
let mult = 1;
let bits = 0;
for (let i = 0; i < p.length; ++i) {
if (msk & (1 << i)) {
++bits;
mult *= p[i];
}
}
const cur = Math.floor(r / mult);
// Непарна кількість множників — додаємо, парна — віднімаємо
if (bits % 2 === 1) {
sum += cur;
} else {
sum -= cur;
}
}
return r - sum;
}
func solve(n, r int) int {
// Знаходимо різні прості множники числа n
var p []int
for i := 2; i*i <= n; i++ {
if n%i == 0 {
p = append(p, i)
for n%i == 0 {
n /= i
}
}
}
if n > 1 {
p = append(p, n)
}
sum := 0
// Перебираємо всі непорожні підмножини множників
for msk := 1; msk < (1 << len(p)); msk++ {
mult := 1
bits := 0
for i := 0; i < len(p); i++ {
if msk&(1<<i) != 0 {
bits++
mult *= p[i]
}
}
cur := r / mult
// Непарна кількість множників — додаємо, парна — віднімаємо
if bits%2 == 1 {
sum += cur
} else {
sum -= cur
}
}
return r - sum
}
Асимптотика розв'язку — .
Кількість цілих чисел на заданому проміжку, кратних хоча б одному із заданих чисел
Дано чисел і число . Потрібно підрахувати кількість цілих чисел на проміжку , кратних хоча б одному з .
Алгоритм розв'язання майже ідентичний до попередньої задачі — будуємо формулу включення-виключення для чисел , тобто кожен доданок у цій формулі — це кількість чисел, що діляться на задану підмножину чисел (іншими словами, що діляться на їхнє найменше спільне кратне).
Отже, ми переберемо всі підмножин цілих чисел за операцій, щоб знайти їхнє найменше спільне кратне, додаючи або віднімаючи кількість його кратних на проміжку. Асимптотика — .
Кількість рядків, що задовольняють заданий взірець
Розглянемо взірців рядків однакової довжини, що складаються лише з літер () або знаків питання. Також задано число . Рядок відповідає взірцю, якщо він має таку саму довжину, що й взірець, і на кожній позиції або відповідні символи рівні, або символ у взірці — це знак питання. Завдання — підрахувати кількість рядків, що відповідають рівно взірцям (перша задача) та хоча б взірцям (друга задача).
Спершу зауважимо, що ми легко можемо підрахувати кількість рядків, які задовольняють одразу всі задані взірці. Для цього просто «накладемо» взірці: переберемо позиції («слоти») і подивимося на позицію в усіх взірцях. Якщо всі взірці мають на цій позиції знак питання, то символ може бути будь-якою літерою від до . Інакше символ цієї позиції однозначно визначається тими взірцями, які не містять знака питання.
Навчимося тепер розв'язувати першу версію задачі: коли рядок має задовольняти рівно взірців.
Щоб розв'язати її, переберемо й зафіксуємо певну підмножину із множини взірців, що складається з взірців. Тоді нам треба підрахувати кількість рядків, які задовольняють цю множину взірців, і лише її, тобто не відповідають жодному іншому взірцю. Ми скористаємося принципом включення-виключення дещо інакше: підсумуємо по всіх надмножинах (підмножинах із вихідної множини рядків, що містять ), і або додаємо до поточної відповіді, або віднімаємо від неї кількість рядків:
Де — це кількість рядків, що відповідають (принаймні ).
(Якщо вам важко це збагнути, можете спробувати намалювати діаграми Венна.)
Якщо ми підсумуємо по всіх , то отримаємо остаточну відповідь:
Однак асимптотика цього розв'язку — . Щоб її покращити, зауважимо, що різні обчислення дуже часто мають спільні множини .
Ми обернемо формулу включення-виключення і підсумовуватимемо по множинах . Тепер стає зрозуміло, що та сама множина буде врахована в обчисленні для множин з однаковим знаком .
Тепер наш розв'язок має асимптотику .
Тепер розв'яжемо другу версію задачі: знайти кількість рядків, що відповідають хоча б взірцям.
Звісно, ми можемо просто скористатися розв'язком першої версії задачі й додати відповіді для множин розміром більшим за . Однак можна помітити, що в цій задачі множина враховується у формулі для всіх множин розміром , що містяться в . З огляду на це, ми можемо записати ту частину виразу, що множиться на , як:
Звернувшись до Грема (Graham, Knuth, Patashnik. "Concrete mathematics" [1998] ), ми бачимо добре відому формулу для біноміальних коефіцієнтів:
Застосувавши її тут, ми знаходимо, що вся сума біноміальних коефіцієнтів зводиться до:
Таким чином, для цієї задачі ми також отримали розв'язок з асимптотикою :
Кількість способів дістатися з однієї клітинки до іншої
Є поле , і його клітинок — непрохідні стіни. Робот спочатку перебуває у клітинці (нижня ліва). Робот може рухатися лише праворуч або вгору, і врешті він має дістатися до клітинки , оминаючи всі перешкоди. Потрібно підрахувати кількість способів, як він може це зробити.
Припустимо, що розміри та дуже великі (скажімо, ), а число мале (близько ).
Поки що відсортуємо перешкоди за їхньою координатою , а у разі рівності — за координатою .
Також просто навчимося розв'язувати задачу без перешкод: тобто навчимося підраховувати кількість способів дістатися з однієї клітинки до іншої. По одній осі нам треба пройти клітинок, а по іншій — клітинок. З простої комбінаторики отримуємо формулу за допомогою біноміальних коефіцієнтів:
Тепер, щоб підрахувати кількість способів дістатися з однієї клітинки до іншої, оминаючи всі перешкоди, можна скористатися включенням-виключенням для розв'язання оберненої задачі: підрахувати кількість способів пройти дошкою, наступивши на підмножину перешкод (і відняти її від загальної кількості способів).
Перебираючи підмножину перешкод, на які ми наступимо, щоб підрахувати кількість способів зробити це, просто перемножуємо кількість усіх шляхів від початкової клітинки до першої з вибраних перешкод, від першої перешкоди до другої, і так далі, а потім додаємо або віднімаємо це число від відповіді відповідно до стандартної формули включення-виключення.
Однак це знову буде неполіноміальним за складністю .
Ось поліноміальний розв'язок:
Ми скористаємося динамічним програмуванням. Для зручності додамо на початок, а — у кінець масиву перешкод. Обчислимо числа — кількість способів дістатися від початкової точки (-вої) до -тої, не наступаючи на жодну іншу перешкоду (окрім , звісно). Ми обчислимо це число для всіх клітинок-перешкод, а також для кінцевої.
Забудьмо на секунду про перешкоди і просто підрахуємо кількість шляхів із клітинки до . Нам треба розглянути деякі «погані» шляхи — ті, що проходять через перешкоди, — і відняти їх від загальної кількості способів дістатися з до .
Розглядаючи перешкоду між та (), на яку можна наступити, ми бачимо кількість шляхів із до , що проходять через , у яких є першою перешкодою між стартом та . Ми можемо обчислити це як: , помножене на кількість довільних шляхів із до . Кількість «поганих» способів можна підрахувати, підсумувавши це для всіх між та .
Ми можемо обчислити за для перешкод, тож цей розв'язок має складність .
Кількість взаємно простих четвірок
Дано чисел: . Потрібно підрахувати кількість способів вибрати чотири числа так, щоб їхній спільний найбільший спільний дільник дорівнював одиниці.
Ми розв'яжемо обернену задачу — обчислимо кількість «поганих» четвірок, тобто четвірок, у яких усі числа діляться на якесь число .
Ми скористаємося принципом включення-виключення, підсумовуючи по всіх можливих групах із чотирьох чисел, що діляться на дільник .
де — кількість простих чисел у розкладі числа на множники, а — кількість четвірок, що діляться на .
Щоб обчислити функцію , треба лише підрахувати кількість кратних (як згадувалося в попередній задачі) і скористатися біноміальними коефіцієнтами, щоб підрахувати кількість способів вибрати чотири з них.
Таким чином, за формулою включення-виключення ми додаємо кількість груп із чотирьох чисел, що діляться на просте число, потім віднімаємо кількість четвірок, що діляться на добуток двох простих, додаємо четвірки, що діляться на три прості, і так далі.
Кількість гармонічних трійок
Дано число . Потрібно підрахувати кількість трійок , які задовольняють одну з таких умов:
- або ,
- або .
По-перше, одразу перейдемо до оберненої задачі — тобто підрахуємо кількість негармонічних трійок.
По-друге, зауважимо, що будь-яка негармонічна трійка складається з пари взаємно простих чисел і третього числа, яке не є взаємно простим хоча б з одним із цієї пари.
Таким чином, кількість негармонічних трійок, що містять , дорівнює кількості цілих чисел від до , взаємно простих з , помноженій на кількість цілих чисел, не взаємно простих з .
Або
або
В обох цих випадках трійку буде враховано двічі. Перший випадок буде враховано, коли і коли . Другий випадок буде враховано, коли і коли . Тому, щоб обчислити кількість негармонічних трійок, ми підсумовуємо це обчислення по всіх від до і ділимо його на .
Тепер залишилося лише навчитися підраховувати кількість взаємно простих з на проміжку . Хоча ця задача вже згадувалася, наведений вище розв'язок тут не підходить — він вимагав би факторизації кожного з цілих чисел від до , а потім перебору всіх підмножин цих простих чисел.
Швидший розв'язок можливий за допомогою такої модифікації решета Ератосфена:
-
По-перше, знаходимо всі числа на проміжку такі, що їхній простий розклад на множники не включає жоден простий множник двічі. Нам також треба знати для цих чисел, скільки множників вони містять.
- Для цього ми вестимемо масив для зберігання кількості простих чисел у розкладі на множники, та масив , щоб позначити, чи містить кожен множник не більше одного разу (), чи ні (). Перебираючи від до , якщо ми доходимо до числа, у якого дорівнює , то це просте число і його дорівнює .
- Під час решета Ератосфена ми перебиратимемо від до . Обробляючи просте число, ми проходимо всі його кратні й збільшуємо їхні . Якщо одне з цих кратних є кратним квадрата , то ми можемо встановити як false.
-
По-друге, нам треба обчислити відповідь для всіх від до , тобто масив — кількість цілих чисел, не взаємно простих з .
- Для цього згадаймо, як працює формула включення-виключення — фактично тут ми реалізуємо ту саму концепцію, але з оберненою логікою: ми перебираємо компонент (добуток простих чисел із розкладу на множники) і додаємо або віднімаємо його доданок у формулі включення-виключення для кожного з його кратних.
- Отже, припустимо, ми обробляємо число таке, що , тобто воно бере участь у формулі включення-виключення. Перебираємо всі числа, кратні , і або додаємо, або віднімаємо від їхнього (знак залежить від : якщо непарне, то ми маємо додати, інакше — відняти).
Ось реалізація:
- C++
- Python
- TypeScript
- Go
int n;
bool good[MAXN];
int deg[MAXN], cnt[MAXN];
long long solve() {
memset (good, 1, sizeof good);
memset (deg, 0, sizeof deg);
memset (cnt, 0, sizeof cnt);
long long ans_bad = 0;
for (int i=2; i<=n; ++i) {
if (good[i]) {
if (deg[i] == 0) deg[i] = 1;
for (int j=1; i*j<=n; ++j) {
if (j > 1 && deg[i] == 1)
if (j % i == 0)
good[i*j] = false;
else
++deg[i*j];
cnt[i*j] += (n / i) * (deg[i]%2==1 ? +1 : -1);
}
}
ans_bad += (cnt[i] - 1) * 1ll * (n-1 - cnt[i]);
}
return (n-1) * 1ll * (n-2) * (n-3) / 6 - ans_bad / 2;
}
def solve(n: int) -> int:
good = [True] * (n + 1) # чи містить i кожен простий множник не більше разу
deg = [0] * (n + 1) # кількість простих чисел у розкладі i
cnt = [0] * (n + 1) # кількість чисел, НЕ взаємно простих з i
ans_bad = 0
for i in range(2, n + 1):
if good[i]:
if deg[i] == 0:
deg[i] = 1 # i — просте число
j = 1
while i * j <= n:
if j > 1 and deg[i] == 1:
if j % i == 0:
good[i * j] = False
else:
deg[i * j] += 1
# Додаємо або віднімаємо доданок включення-виключення
cnt[i * j] += (n // i) * (1 if deg[i] % 2 == 1 else -1)
j += 1
ans_bad += (cnt[i] - 1) * (n - 1 - cnt[i])
return (n - 1) * (n - 2) * (n - 3) // 6 - ans_bad // 2
function solve(n: number): bigint {
const good = new Array<boolean>(n + 1).fill(true); // чи містить i кожен простий множник не більше разу
const deg = new Array<number>(n + 1).fill(0); // кількість простих чисел у розкладі i
const cnt = new Array<number>(n + 1).fill(0); // кількість чисел, НЕ взаємно простих з i
let ansBad = 0n;
for (let i = 2; i <= n; ++i) {
if (good[i]) {
if (deg[i] === 0) {
deg[i] = 1; // i — просте число
}
for (let j = 1; i * j <= n; ++j) {
if (j > 1 && deg[i] === 1) {
if (j % i === 0) {
good[i * j] = false;
} else {
++deg[i * j];
}
}
// Додаємо або віднімаємо доданок включення-виключення
cnt[i * j] += Math.floor(n / i) * (deg[i] % 2 === 1 ? 1 : -1);
}
}
ansBad += BigInt(cnt[i] - 1) * BigInt(n - 1 - cnt[i]);
}
return BigInt(n - 1) * BigInt(n - 2) * BigInt(n - 3) / 6n - ansBad / 2n;
}
func solve(n int) int64 {
good := make([]bool, n+1) // чи містить i кожен простий множник не більше разу
deg := make([]int, n+1) // кількість простих чисел у розкладі i
cnt := make([]int, n+1) // кількість чисел, НЕ взаємно простих з i
for i := range good {
good[i] = true
}
var ansBad int64 = 0
for i := 2; i <= n; i++ {
if good[i] {
if deg[i] == 0 {
deg[i] = 1 // i — просте число
}
for j := 1; i*j <= n; j++ {
if j > 1 && deg[i] == 1 {
if j%i == 0 {
good[i*j] = false
} else {
deg[i*j]++
}
}
// Додаємо або віднімаємо доданок включення-виключення
sign := -1
if deg[i]%2 == 1 {
sign = 1
}
cnt[i*j] += (n / i) * sign
}
}
ansBad += int64(cnt[i]-1) * int64(n-1-cnt[i])
}
return int64(n-1)*int64(n-2)*int64(n-3)/6 - ansBad/2
}
Асимптотика нашого розв'язку — , бо майже для кожного числа до ми робимо ітерацій у вкладеному циклі.
Кількість перестановок без нерухомих точок (безлади)
Доведіть, що кількість перестановок довжини без нерухомих точок (тобто жодне число не стоїть на позиції — таку перестановку також називають безладом) дорівнює такому числу:
і приблизно дорівнює:
(якщо заокруглити цей вираз до найближчого цілого — отримаємо рівно кількість перестановок без нерухомих точок)
Позначимо через множину перестановок довжини з нерухомою точкою на позиції () (тобто елемент стоїть на позиції ).
Тепер ми скористаємося формулою включення-виключення, щоб підрахувати кількість перестановок із хоча б однією нерухомою точкою. Для цього нам треба навчитися підраховувати розміри перетину множин , а саме:
бо якщо ми знаємо, що кількість нерухомих точок дорівнює , то нам відомі позиції елементів перестановки, а всі інші елементів можна розмістити будь-де.
Підставивши це у формулу включення-виключення і враховуючи, що кількість способів вибрати підмножину розміру з множини елементів дорівнює , ми отримуємо формулу для кількості перестановок із хоча б однією нерухомою точкою:
Тоді кількість перестановок без нерухомих точок дорівнює:
Спростивши цей вираз, ми отримуємо точний та наближений вирази для кількості перестановок без нерухомих точок:
(оскільки сума в дужках — це перші доданків розкладу у ряд Тейлора)
Варто зауважити, що схожу задачу можна розв'язати в такий самий спосіб: коли треба, щоб нерухомі точки не були серед перших елементів перестановки (а не серед усіх, як ми щойно розв'язали). Отримана формула така сама, як наведена вище точна формула, але вона йтиме до суми по , а не по .
Задачі для практики
Перелік задач, які можна розв'язати за допомогою принципу включення-виключення:
- UVA #10325 "The Lottery" [difficulty: low]
- UVA #11806 "Cheerleaders" [difficulty: low]
- TopCoder SRM 477 "CarelessSecretary" [difficulty: low]
- TopCoder TCHS 16 "Divisibility" [difficulty: low]
- SPOJ #6285 NGM2 , "Another Game With Numbers" [difficulty: low]
- TopCoder SRM 382 "CharmingTicketsEasy" [difficulty: medium]
- TopCoder SRM 390 "SetOfPatterns" [difficulty: medium]
- TopCoder SRM 176 "Deranged" [difficulty: medium]
- TopCoder SRM 457 "TheHexagonsDivOne" [difficulty: medium]
- SPOJ #4191 MSKYCODE "Sky Code" [difficulty: medium]
- SPOJ #4168 SQFREE "Square-free integers" [difficulty: medium]
- CodeChef "Count Relations" [difficulty: medium]
- SPOJ - Almost Prime Numbers Again
- SPOJ - Find number of Pair of Friends
- SPOJ - Balanced Cow Subsets
- SPOJ - EASY MATH [difficulty: medium]
- SPOJ - MOMOS - FEASTOFPIGS [difficulty: easy]
- Atcoder - Grid 2 [difficulty: easy]
- Codeforces - Count GCD