Курс№8. Комбинаторика0%
Теория · номер 8 из 27

Комбинаторика

Считаем слова и числа, которые можно составить по правилам. Почти всё решается одним и тем же шаблоном на Python, поэтому номер быстро становится своим.

9 разделов~45 минут1 первичный балл
  1. 1Что проверяет номер
  2. 2База: перебор, номер и счётчик
  3. 3Слова по порядку
  4. 4Сколько слов с условием
  5. 5Числа в системе счисления
  6. 6Соседи: приём с заменой
  7. 7Перестановки букв слова
  8. 8Ловушки перед экзаменом
  9. 9Домашка
Раздел 1 из 9

Что проверяет номер

В №8 из заданных букв или цифр составляют все возможные слова или числа, а потом что-то про них спрашивают. Задачи бывают двух видов, и их полезно различать с первого взгляда.

По порядку

Слова выписали по алфавиту и пронумеровали. Спрашивают номер какого-то слова.

Слова длины 3 из букв К, О, Т записаны по алфавиту: ККК, ККО, ККТ, КОК… Под каким номером стоит ТОК?

Без порядка

Никакого списка нет. Спрашивают, сколько слов или чисел подходит под условие.

Сколько существует трёхзначных чисел в пятеричной системе, у которых все цифры разные?

Оба вида решаем одинаково: просим Python перебрать вообще все варианты, а потом проверяем каждый. Компьютер перебирает миллион слов меньше чем за секунду, так что думать, как посчитать хитро, не нужно. Нужно правильно записать условие.

Можно ли решить руками? Можно, через формулы комбинаторики. Но на экзамене это долго, и легко упустить один случай. Руками мы будем считать только маленькие примеры, чтобы понимать, что делает код.

Ответы к примерам

ТОК стоит под номером 22. Чисел с разными цифрами 48. Как это получить, разберём в следующих разделах.

Раздел 2 из 9

База: перебор, номер и счётчик

Тут четыре вещи, на которых держится любое решение №8. Если они понятны, дальше будут только разные условия в if.

1. product: все слова, буквы могут повторяться

Функция product из модуля itertools выдаёт все слова заданной длины из заданных букв. Длину пишем через repeat.

python
from itertools import product

for x in product('АБ', repeat=2):
    print(x)

Получили все 4 слова из букв А и Б длины 2. Обрати внимание на порядок: если буквы в строке записаны по алфавиту, то и слова идут по алфавиту. Это пригодится в задачах «по порядку».

2. join: склеиваем слово в строку

product выдаёт слово как набор букв в скобках. Проверять такое неудобно, поэтому первой строкой в цикле всегда склеиваем его в обычную строку:

python
from itertools import product

for x in product('АБ', repeat=3):
    x = ''.join(x)   # ('А', 'Б', 'А') превращается в 'АБА'
    print(x)

В кавычках перед join стоит то, чем разделять буквы. В №8 разделитель не нужен, поэтому кавычки всегда пустые.

3. permutations: каждая буква только один раз

permutations переставляет буквы местами и никогда не повторяет одну букву дважды. Если длину не указать, используются все буквы. Если нужна другая длина, её пишут через r.

python
from itertools import permutations

for x in permutations('КОТ'):
    print(''.join(x))

# а теперь слова из двух букв
for x in permutations('КОТ', r=2):
    print(''.join(x))

Как выбрать между ними:

Что сказано в условииЧто берём
Ничего особенного, буквы могут повторятьсяproduct
«Все буквы (цифры) различны»permutations
«Составляет слова, переставляя буквы слова …»permutations

Если сомневаешься, бери product. Он подходит почти всегда, а «все буквы разные» можно проверить отдельным условием.

4. Номер слова и количество слов

Это две разные переменные, и их часто путают.

python
from itertools import product

n = 0   # номер слова
k = 0   # сколько слов подошло
for x in product('КОТ', repeat=3):
    x = ''.join(x)
    n += 1              # каждое слово получает свой номер

    if x == 'ТОК':
        print('Номер ТОК:', n)

    if x.count('О') == 1:
        k += 1          # считаем только подходящие

print('Слов ровно с одной О:', k)

Заметь, где стоит print(k): после цикла, без отступа. Если поставить его внутрь, он напечатается 27 раз, и ответом будет только последняя строка. Попробуй: сдвинь последнюю строку на 4 пробела вправо и запусти.

Ловушка

Русская «О» и английская «O» выглядят одинаково, но для Python это разные буквы. Если в product буквы русские, то и в проверках пиши на русской раскладке. Код при ошибке ничего не скажет, просто выдаст неверный ответ.

Попробуй самучебная задача

Все слова длины 3 из букв А и Б записаны по алфавиту и пронумерованы: 1. ААА, 2. ААБ, 3. АБА… Под каким номером стоит слово БАБ?

Подсказка

Порядок слов в выводе product('АБ', repeat=3) и есть порядок в списке.

Попробуй самучебная задача

Сколько существует слов длины 4 из букв А, Б, В, в которых буква А встречается ровно один раз?

Раздел 3 из 9

Слова по порядку

Это задачи, где слова выписаны по алфавиту и пронумерованы. Шаблон тот же, что в разделе 2: перебираем, склеиваем, ведём номер. Ошибаются тут в трёх местах: в порядке букв, в выборе первого или последнего слова и в том, какие слова нумеруют.

Буквы должны идти по алфавиту

product выдаёт слова в том порядке, в каком буквы записаны в строке. Значит, буквы надо записать по алфавиту. Есть два способа:

Если в слове из условия буквы повторяются, например «ПАПА», повторы надо убрать, иначе слова будут дублироваться: sorted(set('ПАПА')).

Ловушка: буква Ё

Для Python «Ё» стоит не между Е и Ж, а отдельно от алфавита. sorted('ЕЁЖ') выдаст Ё, Е, Ж. Если в условии есть Ё, не сортируй, а перепиши буквы руками в правильном порядке.

Ловушка: буквы из примера

В условии обычно показывают начало списка: АААА, АААБ… В нём видны не все буквы. Бери буквы из самого слова в условии, а не из примера списка.

Первое или последнее слово

Пример. Слова длины 5 из букв слова ЛИСТ выписаны по алфавиту. Под каким номером стоит последнее слово с чётным номером, которое не начинается с Т и содержит ровно две буквы И?

python
from itertools import product

n = 0
for x in product(sorted('ЛИСТ'), repeat=5):
    x = ''.join(x)
    n += 1
    # чётный номер, первая буква не Т, ровно две И
    if n % 2 == 0 and x[0] != 'Т' and x.count('И') == 2:
        print(n, x)

Вывод длинный, нужна только последняя строка: 708 СТИИТ. Слово рядом с номером печатаем, чтобы глазами проверить, что условие записано правильно. Это хорошая привычка.

Когда нумеруют не все слова

Иногда в условии сказано, что пронумерованы только слова с каким-то свойством, например «без двух одинаковых букв подряд». Тогда n += 1 переезжает внутрь if: номер получают только подходящие слова.

python
from itertools import product

n = 0
for x in product('АБВ', repeat=4):
    x = ''.join(x)
    # нумеруем только слова без двух одинаковых букв подряд
    if 'АА' not in x and 'ББ' not in x and 'ВВ' not in x:
        n += 1
        if x == 'ВАБА':
            print(n)
Попробуй самучебная задача

Все слова длины 4 из букв слова РОЗА записаны по алфавиту и пронумерованы. Под каким номером стоит первое слово с нечётным номером, которое начинается с З и не содержит буквы А?

Подсказка

В слове РОЗА все буквы разные, так что sorted('РОЗА') хватит. Получится А, З, О, Р.

Раздел 4 из 9

Сколько слов с условием

Здесь списка нет, нужно посчитать подходящие слова. Работает счётчик k, и почти всё решение сводится к тому, чтобы правильно записать условие в if. Ниже проверки, которые встречаются чаще всего.

УсловиеКак записать
Начинается с Пx[0] == 'П'
Не начинается с А, Е, Оx[0] not in 'АЕО'
Заканчивается на гласнуюx[-1] in 'АЕО'
Ровно две буквы Кx.count('К') == 2
Есть сочетание СОК'СОК' in x
Нет двух К подряд'КК' not in x
Все буквы разныеlen(set(x)) == len(x)
Ровно две гласныеsum(x.count(c) for c in 'ЕО') == 2

Две проверки стоит разобрать.

Все буквы разные. set(x) убирает повторы. Если после этого длина не изменилась, повторов не было.

Сколько гласных. x.count('Е') + x.count('О') работает, но если гласных пять, строка выйдет длинной. Запись sum(x.count(c) for c in 'ЕО') делает то же самое: считает каждую букву из списка и складывает.

Не путай

x[0] not in 'АЕО' проверяет одну первую букву: она не А, не Е и не О. А 'СОК' in x ищет три буквы подряд где угодно в слове. Это разные проверки.

Пример. Сколько слов длины 5 можно составить из букв слова ПЕСОК, если слово начинается с согласной, заканчивается гласной и все буквы в нём разные?

python
from itertools import product

k = 0
for x in product('ПЕСОК', repeat=5):
    x = ''.join(x)
    if x[0] not in 'ЕО' and x[-1] in 'ЕО' and len(set(x)) == len(x):
        k += 1
print(k)

Ответ 36. Руками: первая согласная (П, С или К) выбирается 3 способами, последняя гласная 2 способами, оставшиеся три буквы расставляются на три места 3 · 2 · 1 = 6 способами. Итого 3 · 2 · 6 = 36.

«Можно получить перестановкой букв слова»

Такая формулировка путает: кажется, что нужен permutations. На самом деле она значит, что в слове те же буквы в том же количестве, просто в другом порядке. Проще всего это проверить так: если отсортировать буквы обоих слов, получится одно и то же.

python
from itertools import product

k = 0
for x in product('АМР', repeat=4):
    x = ''.join(x)
    # те же буквы, что в слове РАМА, в любом порядке
    if sorted(x) == sorted('РАМА'):
        k += 1
print(k)
Попробуй самучебная задача

Сколько существует слов длины 5 из букв К, О, Д, А, в которых ровно две гласные и нет двух букв К подряд?

Раздел 5 из 9

Числа в системе счисления

Вместо слов тут числа: «сколько пятизначных восьмеричных чисел…». Для Python это те же слова, только буквы теперь цифры. Нового тут три вещи.

1. Цифры системы счисления

Для восьмеричной системы цифры это '01234567'. Для системы больше десяти после 9 идут буквы: в шестнадцатеричной это 0–9 и a–f. Чтобы не выписывать их руками, есть готовая строка:

python
from string import printable

print(printable[:8])    # цифры восьмеричной системы
print(printable[:16])   # цифры шестнадцатеричной

Буквы в printable маленькие: a, b, c. Если в проверках пишешь цифру-букву, пиши её тоже маленькой.

2. Число не начинается с нуля

product об этом не знает и спокойно составит 00123. Это уже не пятизначное число, поэтому в каждой задаче на числа первая проверка x[0] != '0'.

Ловушка

Пиши '0' в кавычках. x это строка, и x[0] тоже строка. Сравнение x[0] != 0 всегда даёт True, и числа с нуля пролезут в ответ.

3. Чётные и нечётные цифры

Если цифр мало, их можно перечислить: нечётные восьмеричные это '1357'. Для любой системы работает перевод цифры в число: int(c, 16) переводит шестнадцатеричную цифру c в обычное число, а дальше проверяем остаток от деления на 2.

Пример. Сколько существует пятизначных восьмеричных чисел, в которых ровно одна цифра 7 и не больше двух цифр, больших 4?

python
from itertools import product
from string import printable

k = 0
for x in product(printable[:8], repeat=5):
    x = ''.join(x)
    if x[0] != '0' and x.count('7') == 1 and sum(x.count(c) for c in '567') <= 2:
        k += 1
print(k)

А так считаются нечётные цифры в любой системе. Например, четырёхзначные шестеричные числа ровно с двумя нечётными цифрами:

python
from itertools import product
from string import printable

k = 0
for x in product(printable[:6], repeat=4):
    x = ''.join(x)
    odd = [c for c in x if int(c, 6) % 2 == 1]   # все нечётные цифры числа
    if x[0] != '0' and len(odd) == 2:
        k += 1
print(k)

Если в условии сказано «все цифры различны», бери permutations с длиной r: повторов не будет сразу, отдельно проверять не нужно.

Попробуй самучебная задача

Сколько существует трёхзначных шестнадцатеричных чисел, у которых все цифры разные, а последняя цифра чётная?

Подсказка

Чётные шестнадцатеричные цифры: 0, 2, 4, 6, 8, a, c, e.

Раздел 6 из 9

Соседи: приём с заменой

Частое условие: «никакие две нечётные цифры не стоят рядом» или «нечётная цифра не стоит рядом с 7». Проверять каждую пару соседей вручную долго. Есть приём короче.

Идея

Сделаем копию числа и заменим в ней все нечётные цифры на одну и ту же, например на 1. Число 152063 превратится в 112061. Теперь «две нечётные рядом» это просто '11' в строке. Какие именно цифры там стояли, уже неважно.

Пример. Сколько существует шестизначных чисел в семеричной системе, в которых никакие две нечётные цифры не стоят рядом?

python
from itertools import product
from string import printable

k = 0
for x in product(printable[:7], repeat=6):
    x = ''.join(x)
    x1 = x                        # копия, исходное число не портим
    for c in '35':
        x1 = x1.replace(c, '1')   # все нечётные превращаем в 1
    if x[0] != '0' and '11' not in x1:
        k += 1
print(k)

Ответ 43728. Тройку и пятёрку заменили на единицу, а сама единица и так единица.

Ловушка: replace не меняет строку

x1.replace('3', '1') не трогает x1, а возвращает новую строку. Если не написать x1 = перед ним, замена потеряется. Поэтому в цикле всегда x1 = x1.replace(...).

Почему заменяем в копии, а не в самом x? Исходное число ещё нужно: проверить, что оно не начинается с нуля, а иногда и вывести его в ответ. После замен по x1 уже не понять, какое число было.

Чётные и нечётные сразу

«Никакие две чётные и никакие две нечётные не стоят рядом»: заменяем нечётные на 1, чётные на 2 и проверяем две строки.

python
for c in '1357':
    x1 = x1.replace(c, '1')
for c in '02468':
    x1 = x1.replace(c, '2')
if '11' not in x1 and '22' not in x1:
    ...

Нечётная рядом с конкретной цифрой

«Ни одна нечётная цифра не стоит рядом с 7» в восьмеричной системе. Семёрку не заменяем, остальные нечётные превращаем в 1. Плохие соседи тогда 17, 71 и 77.

python
from itertools import product
from string import printable

k = 0
for x in product(printable[:8], repeat=5):
    x = ''.join(x)
    x1 = x
    for c in '35':
        x1 = x1.replace(c, '1')   # все нечётные, кроме 7, в 1
    if x[0] != '0' and '17' not in x1 and '71' not in x1 and '77' not in x1:
        k += 1
print(k)
Ловушка

Семёрка тоже нечётная. Две семёрки рядом это тоже «нечётная рядом с 7», поэтому '77' проверяем обязательно. Без этой проверки получится неверный ответ.

Запасной способ: список плохих пар

Если приём с заменой не придумывается, можно выписать все запрещённые пары и проверить, что ни одной нет в числе:

python
bad = [a + b for a in '135' for b in '135']   # 11, 13, 15, 31, ... все пары нечётных
if all(p not in x for p in bad):
    ...

all(...) вернёт True, только если каждая проверка внутри True. Ответ получится тот же, просто замена короче и в ней проще не ошибиться.

Попробуй самучебная задача

Сколько существует пятизначных девятеричных чисел, у которых все цифры разные и никакие две чётные и никакие две нечётные цифры не стоят рядом?

Подсказка

В девятеричной системе цифры от 0 до 8, так что чётных пять (0, 2, 4, 6, 8), нечётных четыре.

Раздел 7 из 9

Перестановки букв слова

«Вася составляет слова, переставляя буквы слова КАРТА». Тут нужен permutations: буквы только переставляются, новых не появляется. Но есть подвох.

Повторяющиеся буквы

В слове КАРТА две буквы А. Для Python это разные элементы: первая А и вторая А. Если их поменять местами, Python посчитает это новым словом, хотя для нас слово то же самое.

python
from itertools import permutations

print(len(list(permutations('КАРТА'))))   # с повторами
print(len(set(permutations('КАРТА'))))    # различные слова

set убирает одинаковые слова, и остаются 60 различных. Если в слове есть повторяющиеся буквы, set нужен обязательно.

Ловушка: set не туда

permutations(set('КАРТА')) это ошибка. Так set выкинет вторую А ещё до перестановок, и Python будет переставлять слово из четырёх букв КАРТ. Получится 24 вместо 60. set ставим снаружи: set(permutations('КАРТА')).

Пример. Сколько различных слов можно получить, переставляя буквы слова БАНАН, если две буквы Н не должны стоять рядом?

python
from itertools import permutations

k = 0
for x in set(permutations('БАНАН')):
    x = ''.join(x)
    if 'НН' not in x:
        k += 1
print(k)

Ответ 18. Проверим руками: всего различных слов 5! / (2! · 2!) = 30. Если склеить НН в один блок, останется переставить 4 элемента с двумя А: 4! / 2! = 12. Значит, без НН рядом 30 − 12 = 18.

Ловушка с count

«Ровно один раз встречаются две гласные подряд». Хочется заменить все гласные на одну букву и проверить count('ГГ') == 1. Но count считает вхождения, которые не пересекаются:

python
print('ГГГ'.count('ГГ'))

В «ГГГ» две пары гласных подряд (первая со второй и вторая с третьей), а count насчитает одну. Поэтому добавляем проверку, что трёх гласных подряд нет.

python
from itertools import permutations

k = 0
for x in set(permutations('КАКАО')):
    x = ''.join(x)
    x1 = x
    for c in 'АО':
        x1 = x1.replace(c, 'Г')   # все гласные в одну букву
    # ровно одна пара гласных подряд и нет трёх подряд
    if x1.count('ГГ') == 1 and 'ГГГ' not in x1:
        k += 1
print(k)

Ответ 18. Без проверки на 'ГГГ' получилось бы 27, и это была бы ошибка.

Попробуй самучебная задача

Сколько различных слов можно получить, переставляя буквы слова КОЛОБОК, если слово должно начинаться и заканчиваться согласной?

Раздел 8 из 9

Ловушки перед экзаменом

Всё, на чём теряют балл в №8, одним списком. Пробеги глазами перед пробником.

ЛовушкаКак не попасться
Русская и английская буква выглядят одинаково (О, А, С, Е, К…)Все буквы в коде набирай в одной раскладке, той же, что в product
Буква Ё в sortedЕсли есть Ё, переписывай алфавит руками
В примере списка видны не все буквыБери буквы из слова в условии
Повторяющиеся буквы в алфавите для productsorted(set('ПАПА'))
Число с нуляx[0] != '0', ноль в кавычках
Буквы-цифры в printable маленькиеВ проверках пиши 'a', а не 'A'
Номер и счётчик перепутаныНомер растёт на каждом слове, счётчик только внутри if
print внутри цикла, когда нужно количествоКоличество печатаем после цикла, без отступа
Первое или последнее словоПервое: break после вывода. Последнее: последняя строка вывода
Замена в самом xМеняем копию x1, и всегда x1 = x1.replace(...)
«Нечётная рядом с 7»7 тоже нечётная, '77' тоже плохо
Повторяющиеся буквы в permutationsset(permutations(...)), но не permutations(set(...))
count не видит пересеченийВместе с count('ГГ') == 1 проверяй 'ГГГ' not in x1

Как проверить себя за 30 секунд

Перед тем как вписать ответ, выведи рядом с номером или вместо счётчика сами слова: print(n, x) или print(x). Посмотри на последние несколько. Если глазами видно, что слово не подходит под условие, значит, в if ошибка. Это ловит большинство ошибок из таблицы выше.

Раздел 9 из 9

Домашка

Шесть задач с настоящих экзаменов и из банка ФИПИ. Все они про слова по порядку: это самый частый вид №8 на экзамене последних лет. Решай прямо здесь: кнопка «Решать здесь» открывает редактор, а код каждой задачи сохраняется.

Перед тем как писать код, ответь себе на три вопроса по условию:

Где теряют балл в этих задачах

«Не менее одной» это >= 1, «не более одной» это <= 1, «ровно одна» это == 1. Во второй задаче буква Ь: sorted ставит её правильно, но надёжнее переписать буквы из начала списка. В шестой задаче нужен не номер, а количество, и номер всё равно растёт на каждом слове.

Ещё задачи этого вида лежат в разделе «Задания», №8.

Задачи «Попробуй сам» учебные, в домашке задачи с настоящих экзаменов и из банка ФИПИ. Код в блоках можно менять и запускать, он выполняется прямо в браузере.