Правила суммы и произведения

Осваиваем два фундаментальных правила счёта, на которых стоит вся комбинаторика.

Комбинаторика — раздел дискретной математики, изучающий, сколькими способами можно выбрать, расставить или составить объекты из заданного набора.

Зачем учиться считать варианты

«Сколько паролей длины 8?», «Сколько IP-адресов в подсети?», «Сколько раз выполнится вложенный цикл?», «Какова вероятность угадать?» — за всеми этими вопросами стоит комбинаторика. Оценка числа вариантов — это и анализ сложности алгоритмов (перебор всех перестановок — это факториал вариантов, отсюда «комбинаторный взрыв»), и криптография (стойкость пароля — это число его вариантов), и проектирование. Всё начинается с двух простых правил.

Правило суммы: выбор «или»

Правило суммы: если объект можно выбрать из первого набора m способами или из второго набора n способами, и наборы не пересекаются, то всего вариантов m + n.

Пример: в меню 4 первых блюда и 3 десерта. Если хочется заказать одно блюдо (или первое, или десерт), вариантов 4 + 3 = 7. Ключевое слово — «или» и «не пересекаются». Это та же логика, что объединение непересекающихся множеств: |A ∪ B| = |A| + |B|. Если наборы пересекаются, надо вычесть пересечение (вспоминаем включение-исключение — к нему вернёмся).

Правило произведения: выбор «и»

Правило произведения: если выбор состоит из двух независимых шагов, и первый можно сделать m способами, а второй (при любом исходе первого) n способами, то всю пару шагов можно выполнить m · n способами.

Пример: 4 первых блюда и 3 десерта — на обед берём и то, и другое. Тогда обедов 4 · 3 = 12. Ключевое слово — «и», последовательные независимые шаги. Это в точности мощность декартова произведения: |A × B| = |A| · |B|. Каждый обед — пара (первое, десерт).

Как не перепутать

Простое правило: «или» → складываем, «и» → умножаем. Делаем один выбор из нескольких возможностей — сумма. Делаем несколько выборов подряд — произведение. Это различие — самое частое место ошибок у новичков, поэтому всегда спрашивай себя: «я выбираю одно из, или составляю из нескольких частей?».

Считаем пароли: правило произведения в деле

Сколько паролей длины 3 из строчных латинских букв (26 штук)? На каждую позицию — 26 вариантов, позиций три, выборы независимы: 26 · 26 · 26 = 26³. Проверим перебором на маленьком алфавите, чтобы увидеть правило произведения вживую.

from itertools import product

alphabet = ['a', 'b', 'c']     # маленький алфавит из 3 букв
length = 3

# Перебираем все строки длины 3 из этого алфавита
all_passwords = list(product(alphabet, repeat=length))
print("Всего вариантов перебором:", len(all_passwords))
print("По правилу произведения 3^3 =", 3 ** length)
print("Первые пять:", [''.join(p) for p in all_passwords[:5]])

# Правило суммы: выбрать ОДИН символ — букву (3) или цифру (2)
letters = ['a', 'b', 'c']
digits = ['0', '1']
print("Один символ (буква ИЛИ цифра):", len(letters) + len(digits))

Вывод:

Всего вариантов перебором: 27
По правилу произведения 3^3 = 27
Первые пять: ['aaa', 'aab', 'aac', 'aba', 'abb']
Один символ (буква ИЛИ цифра): 5

Перебор дал ровно 27 = 3³ — правило произведения подтвердилось. А для «одного символа из букв или цифр» сработало правило суммы: 3 + 2 = 5. Заметь, как itertools.product прямо реализует декартово произведение — отсюда и название.

Комбинируем правила

Реальные задачи сочетают оба правила. Сколько автомобильных номеров вида «буква-три цифры-буква», если букв 12, а цифр 10? Это последовательность шагов (правило произведения): 12 · 10 · 10 · 10 · 12 = 12² · 10³ = 144000. А если номер может быть либо такого формата, либо другого — форматы складываются по правилу суммы. Разбивай сложную задачу на шаги (умножаем) и альтернативы (складываем) — и считай по частям.

Дерево вариантов

Наглядная модель правила произведения — дерево вариантов. Корень — старт, из него выходит m ветвей (первый выбор), из каждой — ещё n ветвей (второй выбор). Число «листьев» внизу — это m · n, все возможные исходы. Дерево помогает не запутаться в сложных задачах: рисуешь уровни выборов и считаешь листья. Этот же образ — основа алгоритмов перебора с возвратом (backtracking).

Рекурсия против итерации: одно и то же разными словами

Любую рекурсию можно переписать циклом, и наоборот — это важный практический факт. Рекурсия часто короче и яснее выражает идею (особенно для деревьев и «разделяй и властвуй»), но у неё есть цена: каждый вложенный вызов занимает место в стеке вызовов, и при слишком глубокой рекурсии программа падает с переполнением стека. Python, например, по умолчанию ограничивает глубину примерно тысячей вызовов.

def fact_rec(n):
    return 1 if n == 0 else n * fact_rec(n - 1)   # рекурсивно

def fact_iter(n):
    res = 1
    for k in range(2, n + 1):                      # итеративно
        res *= k
    return res

print("Рекурсия:", fact_rec(6), "| Итерация:", fact_iter(6))

Вывод:

Рекурсия: 720 | Итерация: 720

Обе версии дают одно и то же. Выбор — вопрос вкуса и ограничений: для глубокой рекурсии (обход огромного дерева) безопаснее итеративная версия с явным стеком, а для неглубокой и «древовидной» логики рекурсия читается лучше. Понимание их эквивалентности освобождает: вы выбираете форму записи под задачу, зная, что вычислительно это одно и то же.

Правила в реальных подсчётах: адреса и номера

Закрепим правила на «инженерных» примерах, которые программист встречает напрямую. Сколько устройств можно адресовать в подсети /24 (где под номер хоста отведено 8 бит)? Каждый бит — независимый выбор из двух значений, итого по правилу произведения 2⁸. А сколько коротких артикулов вида «буква-цифра-цифра», если букв 10, а цифр 10?

# Подсеть /24: 8 свободных бит, каждый бит независим (правило произведения)
print("Адресов в подсети /24:", 2 ** 8)

# Артикул вида буква-цифра-цифра: 3 независимых выбора
letters, digits = 10, 10
print("Артикулов «буква-цифра-цифра»:", letters * digits * digits)

# А если артикул — это ЛИБО такой формат, ЛИБО просто 4 цифры (правило суммы)
four_digits = 10 ** 4
print("Всего форматов (правило суммы):", letters * digits * digits + four_digits)

Вывод:

Адресов в подсети /24: 256
Артикулов «буква-цифра-цифра»: 1000
Всего форматов (правило суммы): 11000

Здесь работают оба правила: внутри одного формата перемножаем независимые позиции (произведение), а складывая два альтернативных формата — суммируем (правило суммы). Именно так инженеры заранее оценивают, хватит ли адресного пространства или диапазона артикулов. Понимание этих правил спасает от ситуаций вроде «закончились IP-адреса» — классической проблемы, породившей переход с IPv4 на IPv6.

Типичные ошибки

  • Складывать там, где надо умножать. «Рубашка и брюки» — это произведение (рубашки · брюки), а не сумма. Складываем только взаимоисключающие альтернативы.
  • Применять правило суммы к пересекающимся наборам. Если что-то попадает в оба набора, простое сложение посчитает его дважды — нужно включение-исключение.
  • Забывать про зависимость шагов. Правило произведения требует, чтобы число вариантов второго шага не зависело от исхода первого. Если зависит (например, «без повторов»), считаем аккуратнее — это уже размещения.

Итог

  • Правило суммы: один выбор из непересекающихся наборов — складываем («или»).
  • Правило произведения: несколько независимых шагов — умножаем («и»).
  • Сумма соответствует объединению множеств, произведение — декартову произведению.
  • Сложные задачи разбивают на шаги (умножение) и альтернативы (сложение); помогает дерево вариантов.
Проверьте себя
1. В кафе 5 видов кофе и 4 вида чая. Сколькими способами выбрать ОДИН напиток?
A9
B20
C5
D4
2. Сколько вариантов у пары «футболка и кепка», если футболок 6, а кепок 3?
A9
B18
C6
D3
3. Какому правилу комбинаторики соответствует мощность декартова произведения |A × B| = |A|·|B|?
AПравилу суммы
BПринципу Дирихле
CПравилу произведения
DБиному Ньютона
Поддержать проект