Три математических парадокса, на которых спотыкаются даже самые умные
2026-02-21 16:48 Diff

#статьи

  • 17 июл 2025
  • 0

Три математических парадокса, на которых спотыкаются даже самые умные

Сушим картошку, спасаем узников и играем с бесконечностью.

Иллюстрация: Оля Ежак для Skillbox Media

Автор. На 50% состоит из музыки и ещё на 50% — из любви к интересным людям.

Математический парадокс — это логическое противоречие, когда на первый взгляд правильные рассуждения приводят к абсурдным или взаимоисключающим выводам. Например, вот классический парадокс лжеца: если человек говорит «я сейчас лгу», это создаёт противоречие.

Если утверждение истинно, значит, человек действительно лжёт, и тогда оно становится ложным. И наоборот: если утверждение ложно, значит, человек не лжёт, что делает его истинным. То есть мы попадаем в замкнутый круг, где каждый вывод отрицает сам себя. В подобных противоречиях и заключается суть различных парадоксов.

На этом с теорией мы закончим и перейдём к разбору трёх математических парадоксов, в которые нам было трудно поверить.

Содержание

В вашем сарае лежит 100 килограммов картофеля. Пусть 99% массы каждой картофелины — это вода. Вы оставляете картошку на ночь, и за это время она немного подсыхает. Теперь картофель состоит из 98% воды. Вопрос: сколько килограммов картофеля осталось в вашем сарае?

Казалось бы, если влажность уменьшилась всего на 1%, то и вес картошки должен сократиться на 1 кг. Но давайте немного посчитаем.

Шаг 1. Если картофель на 99% состоит из воды, то оставшийся 1% — это твёрдая часть. То есть в 100 кг будет 99 кг воды и 1 кг сухого вещества.

Шаг 2. На следующий день состав картофеля меняется: теперь в нём 98% воды, а количество сухого вещества остаётся прежним — 1 кг. Однако теперь этот 1 кг составляет уже 2% от общего веса картофеля.

Шаг 3. Решаем простую пропорцию: если 2% массы — это 1 кг сухого вещества, то 100% массы будет составлять 1 кг / 0,02 = 50 кг. То есть после высыхания наш картофель стал весить ровно в два раза меньше.

Слева — 100 кг картофеля: 99% воды и 1 кг твёрдого вещества. В центре — 50 кг после сушки: 98% воды и 1 кг твёрдого вещества. А справа видно, как уменьшение воды удваивает долю твёрдого вещества — с 1% до 2%
Изображение: Cmglee / Wikimedia Commons

Начальник тюрьмы предлагает заключённым игру, победа в которой может принести свободу всем участникам. В тюрьме содержится 100 заключённых, и у каждого на футболке уникальный номер от 1 до 100.

Перед ними — комната с большим шкафом, в котором находится 100 пронумерованных ящиков (от 1 до 100). В каждый ящик начальник случайным образом положил листок с номером одного из заключённых.

Каждый заключённый по очереди заходит в комнату и может открыть 50 из 100 ящиков, пытаясь найти листок со своим номером. Если все 100 справятся с задачей, начальник тюрьмы отпустит их на свободу. Но если хотя бы один не найдёт свой номер, все возвращаются по своим камерам.

Заключённые не могут ничего менять в комнате, оставлять пометки или передавать информацию тем, кто ещё не заходил. Однако перед началом игры им разрешено обсудить правила и договориться о стратегии.

Вопрос: как в такой ситуации должны действовать заключённые, чтобы получить приемлемые шансы на освобождение?

Предположим, первый заключённый выбрал ящик №69. Если под ним не окажется нужного номера, то у него останется ещё 49 попыток
Скриншот: Pygame / Skillbox Media

Перед поиском оптимального решения давайте рассмотрим самый очевидный вариант — когда все заключённые открывают 50 ящиков в случайном порядке, без какой-либо стратегии. В этом случае вероятность того, что один заключённый найдёт свой листок, составляет 1/2. Вероятность того, что два заключённых подряд найдут свои листки, равна 1/2 × 1/2 = 1/4. Для трёх подряд — 1/2 × 1/2 × 1/2 = 1/8.

Если посчитать вероятность для 100 заключённых, то получится (1/2)¹⁰⁰ ≈ 10⁻³⁰. Это невероятно маленькое число, в котором первая значащая цифра появится через нуль и ещё примерно двадцать восемь нулей после запятой. Такую вероятность можно считать почти нулевой, — видимо, именно поэтому начальник тюрьмы и согласился на игру. Но давайте попробуем увеличить шансы.

Предположим, перед началом игры все заключённые договорились использовать стратегию «следуй за цепочкой». Каждый действует так:

  • Сначала он открывает ящик с номером, который совпадает с его номером. Например, заключённый №25 открывает ящик №25.
  • Затем смотрит, какой номер указан на бумажке внутри. Допустим, в ящике № 25 окажется спрятан номер 73.
  • После этого он открывает ящик №73 и снова смотрит, какой номер внутри. Пусть это будет 14.
  • Далее он открывает ящик №14 — и продолжает цепочку до тех пор, пока не найдёт свой номер или не исчерпает 50 попыток.

Эта стратегия работает благодаря тому, что все номера в ящиках распределены без повторений и пропусков — то есть образуют перестановку чисел от 1 до 100. Это значит, что каждый номер встречается ровно один раз, но не обязательно в «своём» ящике.

Такую перестановку удобно представить как набор замкнутых цепочек, или циклов. Цикл — это последовательность переходов по номерам: участник начинает с какого-то ящика, смотрит, какой номер внутри, открывает следующий ящик с этим номером, снова смотрит, что внутри, и так далее — пока не вернётся к начальному номеру. И если длина такого цикла для какого-либо заключённого не будет превышать 50, то он гарантированно найдёт нужный номер за отведённое число попыток.

На скриншоте показан пример, где заключённый №69 начал поиск с соответствующего ящика и за 15 шагов смог найти свой номер
Скриншот: Pygame / Skillbox Media

Если все циклы окажутся короче 51 ящика, то каждый заключённый сможет найти свой номер. Именно эта особенность и даёт шанс на всей команде. Удивительно, но вероятность того, что в случайной перестановке из 100 элементов ни один цикл не будет длиннее 50, составляет примерно 31%. Это подтверждается как математическими расчётами, так и многочисленными симуляциями этой задачи.

Ещё интереснее, что, если увеличить количество заключённых до 1 000, 10 000 или даже 100 000, вероятность успеха почти не изменится и останется на уровне 31%. То есть даже при очень большом числе участников у команды сохраняются шансы на победу. Низкие, но вполне реальные.

Но мы не предлагаем слепо верить этим цифрам — лучше убедиться во всём самостоятельно. Для этого вам понадобится:

  • Установить Python для вашей операционной системы и выбрать редактор кода. Мы будем использовать Visual Studio Code.
  • С помощью менеджера пакетов PIP загрузить библиотеку Pygame. Команда: pip install pygame или py -m pip install pygame.
  • Создать в VS Code новый файл, например, paradox_game.py, и вставить в него код, который мы спрячем ниже под спойлером.
  • Открыть терминал в VS Code и запустить игру. Команда: py paradox_game.py или python paradox_game.py.

Перед вами откроется простая версия игры, в которой можно выбрать любой ящик и двигаться по цепочке в надежде найти свой номер.

Код игры «Парадокс 100 заключённых»

import pygame import random import sys N_PRISONERS = 100 DEFAULT_BOXES_IN_ROW = 20 MIN_BOX_SIZE = 28 MAX_BOX_SIZE = 100 MARGIN_RATIO = 0.12 SIDE_MARGIN_RATIO = 0.14 TOP_BOTTOM_MARGIN = 80 FPS = 60 WHITE = (245, 245, 245) BLACK = (30, 30, 30) GREEN = (56, 200, 100) RED = (220, 50, 50) GRAY = (180, 180, 180) BLUE = (72, 140, 210) YELLOW = (245, 213, 61) ORANGE = (255, 150, 30) MAX_STEPS = N_PRISONERS // 2 pygame.init() pygame.font.init() BASE_FONT_SIZE = 18 BASE_FONT_SMALL = 14 def create_permutation(n): arr = list(range(1, n+1)) random.shuffle(arr) return arr def find_path_and_result(permutation, prisoner, max_steps): path = [] current = prisoner found = False found_at = -1 for i in range(max_steps): path.append(current) if permutation[current - 1] == prisoner: found = True found_at = i + 1 path.append(prisoner) break current = permutation[current - 1] return path, found, found_at def get_grid_params(window_w, window_h): boxes_in_row = DEFAULT_BOXES_IN_ROW num_rows = (N_PRISONERS - 1) // boxes_in_row + 1 side_margin = int(window_w * SIDE_MARGIN_RATIO) usable_w = window_w - 2 * side_margin margin = max(2, int(usable_w / (boxes_in_row * 9))) # динамичный отступ box_size = int(usable_w / (boxes_in_row + (boxes_in_row-1) * MARGIN_RATIO)) box_size = max(MIN_BOX_SIZE, min(box_size, MAX_BOX_SIZE)) grid_w = boxes_in_row * box_size + (boxes_in_row-1) * margin grid_h = num_rows * box_size + (num_rows-1) * margin offset_x = (window_w - grid_w) // 2 offset_y = TOP_BOTTOM_MARGIN return box_size, margin, boxes_in_row, num_rows, offset_x, offset_y def get_box_coords(idx, box_size, margin, boxes_in_row, offset_x, offset_y): row = (idx - 1) // boxes_in_row col = (idx - 1) % boxes_in_row x = offset_x + col * (box_size + margin) + box_size // 2 y = offset_y + row * (box_size + margin) + box_size // 2 return x, y def draw_boxes(screen, box_colors, visible_path, selected_prisoner, permutation, box_size, margin, boxes_in_row, offset_x, offset_y, font_small): for i in range(N_PRISONERS): row = i // boxes_in_row col = i % boxes_in_row x = offset_x + col * (box_size + margin) y = offset_y + row * (box_size + margin) color = box_colors[i] if selected_prisoner and (i+1) == selected_prisoner: color = BLUE if visible_path: if (i+1) == visible_path[-1]: color = YELLOW elif (i+1) in visible_path: color = ORANGE pygame.draw.rect(screen, color, (x, y, box_size, box_size), border_radius=int(box_size*0.22)) num_text = font_small.render(str(i+1), True, BLACK) tx = x + (box_size - num_text.get_width()) // 2 ty = y + (box_size - num_text.get_height()) // 2 screen.blit(num_text, (tx, ty)) def draw_path(screen, path, step, box_size, margin, boxes_in_row, offset_x, offset_y): if not path or len(path) < 2: return draw_to = min(step, len(path)-1) for i in range(draw_to): x1, y1 = get_box_coords(path[i], box_size, margin, boxes_in_row, offset_x, offset_y) x2, y2 = get_box_coords(path[i+1], box_size, margin, boxes_in_row, offset_x, offset_y) pygame.draw.line(screen, ORANGE, (x1, y1), (x2, y2), max(2, box_size // 17)) dx, dy = x2 - x1, y2 - y1 length = max((dx**2 + dy**2) ** 0.5, 1) ux, uy = dx / length, dy / length tip_x = int(x2 - ux * box_size * 0.25) tip_y = int(y2 - uy * box_size * 0.25) pygame.draw.circle(screen, RED, (tip_x, tip_y), max(4, box_size // 10)) def prepare_ui_lines(selected_prisoner, path, permutation, step, found, found_at_step, max_steps, game_over): lines = [] if selected_prisoner is None: lines.append(("Кликни по ящику: выбери свой номер заключённого!", "main", BLACK)) else: cur_step = min(step, len(path)-1) lines.append((f"Заключённый №{selected_prisoner}", "main", BLUE)) if found and found_at_step != -1 and cur_step >= found_at_step: lines.append((f"УРА! Найдено за {found_at_step} шагов!", "main", GREEN)) elif game_over: lines.append(("Игра окончена: не найдено за 50 шагов!", "main", RED)) if path and cur_step < len(path): last_box = path[cur_step] found_number = permutation[last_box - 1] lines.append((f"В ящике №{last_box} спрятан номер {found_number}", "small", BLACK)) lines.append((f"Шаг: {cur_step+1} из {MAX_STEPS}", "small", BLACK)) lines.append(("ENTER -- следующий шаг | SPACE -- новая игра", "small", BLACK)) return lines def draw_ui(screen, lines, font, font_small): window_w, window_h = screen.get_size() line_step = int(font.get_height() * 1.18) total_height = len(lines) * line_step y0 = window_h - total_height - 12 for i, (text, ftype, color) in enumerate(lines): f = font if ftype == "main" else font_small t_surf = f.render(text, True, color) screen.blit(t_surf, (30, y0 + i * line_step)) def main(): WINDOW_START_W = 1320 WINDOW_START_H = 750 screen = pygame.display.set_mode((WINDOW_START_W, WINDOW_START_H), pygame.RESIZABLE) pygame.display.set_caption("Парадокс 100 заключённых") clock = pygame.time.Clock() permutation = create_permutation(N_PRISONERS) box_colors = [WHITE for _ in range(N_PRISONERS)] selected_prisoner = None path = [] step = 0 found = None found_at_step = None game_over = False running = True while running: window_w, window_h = screen.get_size() box_size, margin, boxes_in_row, num_rows, offset_x, offset_y = get_grid_params(window_w, window_h) font_size = max(16, int(BASE_FONT_SIZE * (box_size / 35))) font_small_size = max(11, int(BASE_FONT_SMALL * (box_size / 35))) font = pygame.font.SysFont("consolas", font_size) font_small = pygame.font.SysFont("consolas", font_small_size) lines = prepare_ui_lines(selected_prisoner, path, permutation, step, found, found_at_step, MAX_STEPS, game_over) screen.fill(GRAY) for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.VIDEORESIZE: screen = pygame.display.set_mode(event.size, pygame.RESIZABLE) elif event.type == pygame.MOUSEBUTTONDOWN: if not game_over: mx, my = pygame.mouse.get_pos() for i in range(N_PRISONERS): row = i // boxes_in_row col = i % boxes_in_row x = offset_x + col * (box_size + margin) y = offset_y + row * (box_size + margin) if x <= mx <= x+box_size and y <= my <= y+box_size: selected_prisoner = i + 1 path, found, found_at_step = find_path_and_result(permutation, selected_prisoner, MAX_STEPS) step = 0 game_over = False break elif event.type == pygame.KEYDOWN: if event.key == pygame.K_SPACE: permutation = create_permutation(N_PRISONERS) box_colors = [WHITE for _ in range(N_PRISONERS)] selected_prisoner = None path = [] found = None found_at_step = None step = 0 game_over = False elif event.key == pygame.K_RETURN: if selected_prisoner is not None and path and not game_over: max_show = len(path)-1 if found and step < found_at_step: step += 1 elif not found and step < MAX_STEPS-1: step += 1 elif not found and step >= MAX_STEPS-1: game_over = True cur_step = min(step, len(path)-1) if path else 0 visible_path = path[:cur_step+1] if path else [] draw_boxes(screen, box_colors, visible_path, selected_prisoner, permutation, box_size, margin, boxes_in_row, offset_x, offset_y, font_small) draw_path(screen, path, step, box_size, margin, boxes_in_row, offset_x, offset_y) draw_ui(screen, lines, font, font_small) pygame.display.flip() clock.tick(FPS) pygame.quit() sys.exit() if __name__ == "__main__": main()Интерфейс VS Code после добавления кода и запуска игры
Скриншот: Pygame / Skillbox Media

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

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

Однако если попросить каждого гостя перейти на одну комнату вперёд, то первая комната освободится и в неё можно будет поселить нового гостя.

Получается парадокс: отель полностью заполнен, но для одного нового постояльца место всё равно находится. Это показывает, что у бесконечности нет предела и для неё справедливо равенство ∞ + 1 = ∞.

Случай второй: приходит бесконечное число новых гостей. Просим всех постояльцев нашего переполненного отеля переехать из своей комнаты с номером n в комнату с номером 2n. Например, из комнаты 1 — в комнату 2, из 2 — в 4, из 3 — в 6, из 4 — в 8, из 5 — в 10 и так далее.

После такой перестановки все чётные комнаты окажутся заняты, а нечётные — свободны. Поскольку и тех и других бесконечно много, то в освобождённые нечётные комнаты можно заселить всех прибывших гостей. Так мы разместили одну бесконечность внутри другой: ∞ + ∞ = ∞.

Схема переселения бесконечного числа новых гостей в бесконечно заполненном отеле
Изображение: Jan Beránek / Wikimedia Commons

Случай третий: к отелю подъезжает бесконечное число автобусов с бесконечным числом гостей в каждом. То есть в заполненном отеле нужно разместить бесконечно много бесконечностей.

Чтобы выйти из ситуации, мы можем присвоить каждому гостю уникальный номер. Для этого есть разные способы, и один из них — использование разложения чисел на простые множители. Например, если гость едет в автобусе №i и занимает место №j, то его номер комнаты можно определить по формуле: 2i × 3j. Вот как это вычисляется:

  • Гость из автобуса №1, место №1: 2¹ × 3¹ = 6.
  • Гость из автобуса №2, место №3: 2² × 3³ = 4 × 27 = 108.
  • Гость из автобуса №3, место №2: 2³ × 3² = 8 × 9 = 72.

Также нам придётся переселить всех нынешних жильцов. Для удобства можно считать, что они приехали в автобусе №0, и расселить их по номерам по той же формуле: 2⁰ × 3ʲ = 3ʲ. Например, постояльцу из комнаты №3 придётся переселиться в комнату №27: 2⁰ × 3³ = 1 × 27 = 27.

Получается, мы нашли способ присвоить каждому постояльцу и каждому гостю своё уникальное место. То есть даже «бесконечность в квадрате» может уместиться в бесконечном отеле, если речь идёт о счётной бесконечности — о множестве натуральных чисел: ∞ × ∞ = ∞.

Случай четвёртый: мест для гостей не хватает. Представьте, что к отелю снова подъезжает бесконечное число автобусов и в каждом — бесконечное число гостей. Но теперь у каждого гостя в автобусе уже есть свой уникальный номер — не натуральное число, а любое вещественное число между 0 и 1. То есть это числа, которые можно записать как бесконечные десятичные дроби: 0,333..., 0,5, 0,14159... и так далее.

Поэтому, как бы мы ни нумеровали комнаты, расселить всех гостей не получится: вещественных чисел всегда больше, чем натуральных. Эту особенность впервые сформулировал математик Георг Кантор в теореме о различии мощностей бесконечных множеств. И это ещё один парадокс: не все бесконечности одинаковы — некоторые из них «больше» других.

Представьте телевикторину с тремя дверями. За одной скрывается роскошный автомобиль, за двумя другими — симпатичные козы.

Вы выбираете, скажем, дверь №1. Прежде чем вы успеете её открыть, ведущий открывает одну из двух оставшихся дверей, за которой точно находится коза, — например, дверь №3. Затем он предлагает вам изменить выбор и выбрать дверь №2. Подумайте, стоит ли соглашаться?

Изначально ваши шансы угадать правильную дверь — 1 к 3. Когда ведущий открывает одну из дверей с козой, кажется, что шансы становятся 50 на 50 и менять выбор нет смысла. Но так ли это?
Изображение: Cepheus / Wikimedia Commons

Если вы не уверены в решении — переходите к другой статье, где мы подробно разбираем этот парадокс, предлагаем интерактивную симуляцию на Python и объясняем, почему в него так трудно поверить.

Курс с трудоустройством: «Профессия Data scientist + ИИ» Узнать о курсе