Skip to content

🧮 Увод в комбинаторни конфигурации

Комбинаторните конфигурации представляват основен клон на дискретната математика, който се занимава с изучаването на начини за избиране, подреждане и комбиниране на елементи от дадено множество. Те играят ключова роля в компютърните науки, криптографията и алгоритмите.

📋 Съдържание

  1. Основни понятия
    • Пермутации
    • Комбинации
    • Вариации
  2. Факториел и биномиални коефициенти
    • Определение
    • Формули
    • Примери
  3. Пермутации
    • Пермутации с повтарящи се елементи
    • Пермутации без повтарящи се елементи
    • Алгоритми за генериране на пермутации
  4. Комбинации
    • Разлика между комбинации и пермутации
    • Алгоритми за генериране на комбинации
  5. Вариации
    • Вариации с повторения
    • Вариации без повторения
  6. Приложения

🔑 Основни понятия

Пермутации

Пермутациите представляват различните начини за подреждане на всички елементи от дадено множество. Ако имаме множество с \( n \) елемента, броят на пермутациите е n! (факториел на n).

Пример: Пермутации на множество {1, 2, 3}

{1, 2, 3}, {1, 3, 2}, {2, 1, 3}, {2, 3, 1}, {3, 1, 2}, {3, 2, 1}

Комбинации

Комбинациите представляват различните начини за избиране на k елемента от множество с n елемента, без да се взима предвид редът.

Формула:

C(n, k) = n! / (k! * (n-k)!)

Вариации

Вариациите са подобни на пермутациите, но се използва само подмножество от k елемента, при което редът е от значение.

Формула:

V(n, k) = n! / (n-k)!

🔢 Факториел и биномиални коефициенти

Факториел

Факториелът на число n, означен като n!, е произведението на всички положителни цели числа до n:

n! = n * (n-1) * (n-2) * ... * 1

Пример:

5! = 5 * 4 * 3 * 2 * 1 = 120

Биномиални коефициенти

Биномиалните коефициенти описват броя на комбинациите от n елемента, взети по k:

C(n, k) = n! / (k! * (n-k)!)

Пример:

C(5, 2) = 5! / (2! * (5-2)!) = 120 / (2 * 6) = 10

🔄 Пермутации

Пермутации без повторения

Броят на пермутациите на n различни елемента е n!.

Пример:

P(3) = 3! = 6

Пермутации с повторения

Когато елементите не са уникални, броят на пермутациите се изчислява с:

P(n; k1, k2, ..., kr) = n! / (k1! * k2! * ... * kr!)

Пример:

P(6; 2, 2, 2) = 6! / (2! * 2! * 2!) = 90

🔍 Комбинации

Разлика между комбинации и пермутации

  • Комбинации: Редът няма значение.
  • Пермутации: Редът има значение.

Пример: Генериране на комбинации

#include <iostream>
#include <vector>

void generateCombinations(std::vector<int>& arr, std::vector<int>& combination, int start, int k) {
    if (k == 0) {
        for (int num : combination) std::cout << num << " ";
        std::cout << std::endl;
        return;
    }

    for (int i = start; i < arr.size(); i++) {
        combination.push_back(arr[i]);
        generateCombinations(arr, combination, i + 1, k - 1);
        combination.pop_back();
    }
}

int main() {
    std::vector<int> arr = {1, 2, 3, 4};
    int k = 2;
    std::vector<int> combination;
    generateCombinations(arr, combination, 0, k);
    return 0;
}

🔄 Генериране на пермутации

Наредени по лексикографски ред

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

void generatePermutations(vector<int>& arr) {
    sort(arr.begin(), arr.end());

    do {
        for (int x : arr) cout << x << " ";
        cout << endl;
    } while (next_permutation(arr.begin(), arr.end()));
}

int main() {
    vector<int> arr = {1, 2, 3};
    generatePermutations(arr);
    return 0;
}

Рекурсивно генериране (Heap's Algorithm)

void heapPermutation(vector<int>& arr, int size, int n) {
    if (size == 1) {
        for (int x : arr) cout << x << " ";
        cout << endl;
        return;
    }

    for (int i = 0; i < size; i++) {
        heapPermutation(arr, size - 1, n);

        if (size % 2 == 1)
            swap(arr[0], arr[size - 1]);
        else
            swap(arr[i], arr[size - 1]);
    }
}

🎲 Числа на Каталан

Числата на Каталан се срещат в много комбинаторни задачи: - Брой начини за скобуване на израз с n множители - Брой пътища в мрежа без пресичане на диагонала - Брой двоични дървета с n върха

Формула: \(C_n = \frac{1}{n+1} \binom{2n}{n} = \frac{(2n)!}{(n+1)! \cdot n!}\)

long long catalanDP(int n) {
    vector<long long> catalan(n + 1, 0);
    catalan[0] = catalan[1] = 1;

    for (int i = 2; i <= n; i++) {
        for (int j = 0; j < i; j++) {
            catalan[i] += catalan[j] * catalan[i - j - 1];
        }
    }

    return catalan[n];
}

🔢 Пасков триъгълник

Коефициентите в биномното разлагане образуват пасков триъгълник:

         1
       1   1
     1   2   1
   1   3   3   1
 1   4   6   4   1

Свойство: \(\binom{n}{k} = \binom{n-1}{k-1} + \binom{n-1}{k}\)

vector<vector<long long>> pascalTriangle(int n) {
    vector<vector<long long>> triangle(n);

    for (int i = 0; i < n; i++) {
        triangle[i].resize(i + 1);
        triangle[i][0] = triangle[i][i] = 1;

        for (int j = 1; j < i; j++) {
            triangle[i][j] = triangle[i-1][j-1] + triangle[i-1][j];
        }
    }

    return triangle;
}

🎯 Принцип на включване-изключване

За намиране на броя елементи в обединение на множества: $\(|A \cup B| = |A| + |B| - |A \cap B|\)$

За три множества: $\(|A \cup B \cup C| = |A| + |B| + |C| - |A \cap B| - |A \cap C| - |B \cap C| + |A \cap B \cap C|\)$

Пример: Колко числа от 1 до 100 се делят на 2, 3 или 5?

int divisibleBy2or3or5(int n) {
    int div2 = n / 2;
    int div3 = n / 3;
    int div5 = n / 5;
    int div6 = n / 6;  // LCM(2,3)
    int div10 = n / 10; // LCM(2,5)
    int div15 = n / 15; // LCM(3,5)
    int div30 = n / 30; // LCM(2,3,5)

    return div2 + div3 + div5 - div6 - div10 - div15 + div30;
}

🔄 Генериране на подмножества

Битови маски

void generateSubsets(vector<int>& arr) {
    int n = arr.size();

    // 2^n подмножества
    for (int mask = 0; mask < (1 << n); mask++) {
        cout << "{ ";
        for (int i = 0; i < n; i++) {
            if (mask & (1 << i)) {
                cout << arr[i] << " ";
            }
        }
        cout << "}" << endl;
    }
}

Рекурсивно генериране

void subsetsRecursive(vector<int>& arr, vector<int>& current, int index) {
    if (index == arr.size()) {
        cout << "{ ";
        for (int x : current) cout << x << " ";
        cout << "}" << endl;
        return;
    }

    // Не включваме текущия елемент
    subsetsRecursive(arr, current, index + 1);

    // Включваме текущия елемент
    current.push_back(arr[index]);
    subsetsRecursive(arr, current, index + 1);
    current.pop_back();
}

🎲 Разпределения и сюрекции

Разпределение на n топки в k кутии

  1. Различими топки, различими кутии: \(k^n\)
  2. Различими топки, еднакви кутии: Числа на Стърлинг от втори род
  3. Еднакви топки, различими кутии: \(\binom{n+k-1}{k-1}\)
  4. Еднакви топки, еднакви кутии: Брой разбивания на n

Числа на Стърлинг от втори род

Брой начини да разпределим n различими обекти в k непразни неразличими групи:

long long stirling(int n, int k) {
    vector<vector<long long>> S(n + 1, vector<long long>(k + 1, 0));

    S[0][0] = 1;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= min(i, k); j++) {
            S[i][j] = j * S[i-1][j] + S[i-1][j-1];
        }
    }

    return S[n][k];
}

🧮 Последователности на Фибоначи и комбинаторика

Числата на Фибоначи се явяват в много комбинаторни задачи: - Брой начини да се изкачим стълба от n стъпала (със стъпки от 1 или 2) - Брой двоични низове без две последователни единици

long long countWaysToClimb(int n) {
    if (n <= 2) return n;

    vector<long long> fib(n + 1);
    fib[1] = 1;
    fib[2] = 2;

    for (int i = 3; i <= n; i++) {
        fib[i] = fib[i-1] + fib[i-2];
    }

    return fib[n];
}

🏆 Олимпиадни задачи

  1. Брой пътища в мрежа: От (0,0) до (m,n) само с ходове надясно и нагоре
  2. Отговор: \(\binom{m+n}{m}\)

  3. Броене на Ойлерови пътища: Задача за комбинаторика и теория на графи

  4. Проблем с n ферзи: Разполагане на n ферзи на шахматна дъска n×n така че да не се атакуват

  5. Делене на числа: На колко начина можем да разложим n като сума от положителни числа?

// Брой начини да разложим n като сума
int partitions(int n) {
    vector<int> p(n + 1, 0);
    p[0] = 1;

    for (int i = 1; i <= n; i++) {
        for (int j = i; j <= n; j++) {
            p[j] += p[j - i];
        }
    }

    return p[n];
}

💡 Важни формули

  • \(P(n) = n!\) - пермутации
  • \(V(n, k) = \frac{n!}{(n-k)!}\) - вариации
  • \(C(n, k) = \binom{n}{k} = \frac{n!}{k!(n-k)!}\) - комбинации
  • \(\sum_{k=0}^n \binom{n}{k} = 2^n\) - сума на биномни коефициенти
  • \(\binom{n}{k} = \binom{n}{n-k}\) - симетрия

🎯 Заключение

Комбинаторните конфигурации са в основата на алгоритмичното мислене и се срещат във всички олимпиади. От генериране на пермутации до изчисляване на сложни комбинаторни обекти, тези техники са необходими за всеки състезател. Практикувайте с различни задачи, запомнете основните формули и развивайте интуиция за разпознаване на комбинаторни структури в задачите! 🚀