Статья Автор: Деникина Н.В., Деникин А.В.

Вопрос 15. Паттерны

ЕГЭ по информатике · паттерны

Задание 15

Шесть типов логических выражений с параметром A — программный перебор и аналитика для больших чисел.

1 первичный баллповышенный уровеньперебор + аналитика6 типов + редкиеопасная зона
00

Как устроено решение

Главная идея

Само логическое выражение выносим в отдельную функцию f(A, x) (а для плоскости — f(A, x, y), для отрезков — f(a, b, x), где A — это отрезок). Тогда поиск минимума и максимума A — это один и тот же код с разным направлением перебора.

Три кванта перевода

  • импликация P → Q  =  (not P) or Q
  • «при любом x» (тождественно истинно)  =  all(...)
  • «существует x»  =  any(...)

Шаблон функции выражения

# выносим ВЫРАЖЕНИЕ в функцию
def f(A, x):
    # ... здесь логическое выражение, вернуть True/False
    return (not P) or Q

Перебираем A от большого к малому, первый успех — ответ.

for A in range(10**6, 0, -1):
    if all(f(A, x) for x in X):
        print(A)
        break

Перебираем A от малого к большому, первый успех — ответ.

for A in range(1, 10**6):
    if all(f(A, x) for x in X):
        print(A); break

Область значений x — читай условие

  • Натуральные x («при любом натуральном x») → range(1, N).
  • Неотрицательные целые x («неотрицательное целое», битовые задачи) → range(0, N). Пропустить ноль — частая ошибка.
  • Действительные x (отрезки на числовой прямой) → перебор дробно:
    for i in range(...): x = i/2, иначе теряются граничные точки.
  • Запас по N: для делимости N должно превышать НОК всех чисел из условия (обычно 100000 хватает), для битов — 0..1023.

Ловушки уровня «основа»

  • Импликация — это (not P) or Q, а не P or Q. Можно писать P <= Q, но только если P и Q строго булевы (True/False). При сложном P(Q) берите выражение связанное импликацией в скобки, чтобы изменить приоритет операций.
    x < 2 + y → not Q пишите на Python со скобками (x < 2 + y) <= (not Q)
  • «Тождественно истинно» → all; «тождественно ложно» → not any (или all(not f...)).
01

Отрезки

«A — это отрезок [a; b], ищем его длину»

Признаки в условии

  • «на числовой прямой даны отрезки P, Q…», запись x ∈ P
  • «наименьшую / наибольшую длину отрезка A»
  • x — действительное число (не только целое)

Идея

A — это отрезок, поэтому функция принимает его границы: f(a, b, x).
Перебираем пары границ (a, b), проверяем тождественную истинность и среди подходящих берём минимальную или максимальную длину b - a.
Демо-2026 (P=[25;64], Q=[40;115]) даёт длину 24.

Функция выражения (демо-2026)

def seg(L, R, x):           # принадлежность точки отрезку [L; R]
    return L <= x <= R

def f(a, b, x):             # A — это отрезок [a; b]
    P = seg(25, 64, x)
    Q = seg(40, 115, x)
    A = seg(a, b, x)
    # (x∈P) → (((x∈Q) ∧ ¬(x∈A)) → ¬(x∈P))
    return (not P) or ((not (Q and not A)) or (not P))
best = 10**9
for a in range(0, 200):
  for b in range(a, 200):
    if all(f(a, b, i/2)        # x дробный, шаг 0.5
               for i in range(0, 400)):
      best = min(best, b - a)
print(best)        # → 24

То же, но best = -1 и best = max(best, b - a). Если у A «выколотые» концы — для самого A берут строгое вхождение L < x < R.

Аналитика для больших чисел

Когда границы шестизначные (P=[153697; 780411]…), перебор пар невозможен. Метод в три шага:

  • 1. Упрощаем выражение (раскрываем импликации P → B = ¬P ∨ B) и делим на часть без A и часть с A ((x∈A) или ¬(x∈A)).
  • 2. Рисуем на оси, где «часть без A» ложна — это «дыра», за которую отвечает A.
  • 3. Закрываем дыру: если A-часть это (x∈A) — A обязан накрыть дыру; если ¬(x∈A) — A обязан её не задеть (обойти).

Найденный A проверяем кодом-подстановкой (не перебором):
print(all(f(a, b, x) for x in range(L, R))).

Метод на числовой оси — два примера

Условные обозначения: тонкие скобки — отрезки P и Q (ножки касаются границ); жирная скобка — искомый A; полоса на самой прямой — результат выражения без A (зелёное = истина, красное = дыра, за которую отвечает A).

Пример 1 (часть с A = (x∈A) — A накрывает).
Выражение (x∈P) → ((x∈Q) ∨ (x∈A)) упрощаем: ¬P ∨ Q ∨ A. Часть без A это ¬P ∨ Q; она ложна только там, где x входит в P, но не в Q. Эту дыру обязан накрыть A.

дыра: ¬P ∨ Q = 0 A P Q 01030 5080

A должен накрыть дыру [10; 30] → минимальная длина 20.

Пример 2 (часть с A = ¬(x∈A) — A обходит). Выражение (x∈A) → (x∈P) упрощаем: ¬A ∨ P. Часть без A это P; она ложна везде вне P. На этой дыре нужна ¬(x∈A), то есть A туда заходить нельзя — значит A целиком сидит внутри P.

дыра: вне P нужно ¬A → A нельзя A P 010 5090

A не должен задевать дыру (всё вне P) → A ⊆ [10; 50], максимальная длина 40.

Ловушки

  • Целочисленный перебор x вместо дробного — теряются граничные эффекты.
  • Путаница min/max длины и направления покрытия (пересечение против объединения).
02

Множества

«A — множество: минимум и максимум собираем поэлементно»

Признаки в условии

  • заданы множества P, Q (списком или правилом), элементы — натуральные числа
  • вопрос про количество или сумму элементов A

Идея (шаблон другой!)

Здесь A — не число и не отрезок, а множество, поэтому перебирать A в цикле нельзя. Истинность для каждого x зависит только от того, лежит ли этот x в A. Значит для каждого x независимо решаем: он обязан быть в A, запрещён или свободен. Минимум и максимум собираются в одном проходе.

Функция выражения + сбор A

P = set(range(2, 21, 2))     # пример: {2,4,...,20}
Q = set(range(5, 51, 5))     # пример: {5,10,...,50}

def f(A, x):                 # A — множество; истинность выражения для данного x
    # ((x∈A) → (x∈P)) ∧ ((x∈Q) → ¬(x∈A))
    return ((x in A) <= (x in P)) and ((x in Q) <= (not (x in A)))

A_max, A_min = set(), set()
for x in range(0, 1000):
    can_in  = f({x}, x)      # выражение истинно, если x ЛЕЖИТ в A
    can_out = f(set(), x)    # выражение истинно, если x НЕ лежит в A
    if can_in:               # для МАКСИМУМА берём всё допустимое
        A_max.add(x)
    if not can_out:          # для МИНИМУМА — только обязательное
        A_min.add(x)

print(len(A_max), sum(A_max))    # наибольшее множество: размер и сумма
print(len(A_min), sum(A_min))    # наименьшее множество: размер и сумма

Аналитика для больших чисел

  • перевести в язык включений: (x∈A) → (x∈P) означает A ⊆ P; ¬(x∈Q) → ¬(x∈A) (контрапозиция) означает A ⊆ Q;
  • тогда A ⊆ P ∩ Q — для максимума берём всё пересечение, для минимума — только вынужденные элементы;
  • если множества заданы правилом (кратные m, биты) — задача фактически сводится к делимости (тип 03) или битам (тип 04).

Ловушки

  • Путаница ⊆ и ⊇, неверная контрапозиция.
  • «Наибольшее количество» ≠ «наибольшая сумма» — смотри, что именно спрашивают.
03

Делимость (ДЕЛ)

«ДЕЛ(x, A): x кратно A»

Признаки в условии

  • функция ДЕЛ(n, m) — «n делится на m без остатка»
  • вопрос про наибольшее / наименьшее натуральное A
  • часто множитель-ограничитель ДЕЛ(C, A) (A — делитель числа C)

Идея

A — одно натуральное число, функция f(A, x). Перебор A в обе стороны для минимума/максимума. x — натуральное, поэтому range(1, N), где N больше НОК всех чисел условия.

Функция выражения

def D(n, m):
    return n % m == 0

def f(A, x):
    # ДЕЛ(120, A) ∧ ((ДЕЛ(x,70) ∧ ДЕЛ(x,30)) → ДЕЛ(x,A))
    return D(120, A) and ((D(x, 70) and D(x, 30)) <= D(x, A))
for A in range(1000, 0, -1):
    if all(f(A, x)
           for x in range(1, 100000)):   # x натуральное
        print(A); break
for A in range(1, 1000):
    if all(f(A, x)
           for x in range(1, 100000)):
        print(A); break

Аналитика для больших чисел

  • ДЕЛ(x, A) истинно ⇔ x кратно A. Работаем с множествами кратных.
  • (ДЕЛ(x,m) ∧ ДЕЛ(x,k)) → ДЕЛ(x,A), наибольшее A: посылка истинна при x, кратном НОК(m, k), значит наибольшее A = НОК(m, k) (а при ограничителе ДЕЛ(C,A) — наибольший делитель C, кратный нужному).
  • (ДЕЛ(x,m) ∨ ДЕЛ(x,k)) → ДЕЛ(x,A): A = НОД(m, k) (пример: для 40 и 64 наибольшее A = НОД = 8).
  • ДЕЛ(x,A) → (ДЕЛ(x,m) ∨ ДЕЛ(x,k)), наименьшее A: каждое кратное A делится на m или k — ищем через простые множители (пример: для 19 и 15 наименьшее A = 285).
  • раскладываем m, k на простые: для НОК берём max степеней, для НОД — min; полезно НОД·НОК = m·k.

Ловушки

  • Путают НОД и НОК; забывают множитель-ограничитель ДЕЛ(C, A), который сужает A до делителей C.
  • Слишком маленький диапазон x (должен превышать НОК всех чисел).
  • A для делимости начинается с 1 (деление на 0 недопустимо).
04

Битовые операции

«Поразрядная конъюнкция x & K»

Признаки в условии

  • обозначение m & n / «поразрядная конъюнкция»
  • условия x & K = 0 / x & K ≠ 0
  • ответ — неотрицательное целое (часто степень двойки)

Идея

A — неотрицательное целое. x тоже неотрицательное, поэтому перебор с нуля: range(0, 1024) (10 бит обычно хватает). Минимум/максимум A — смена направления.

Функция выражения

def f(A, x):
    a = (x & 51) != 0       # СКОБКИ обязательны: & ниже по приоритету, чем !=
    b = (x & A)  == 0
    c = (x & 25) != 0
    # (x&51≠0) → ((x&A=0) → (x&25≠0))
    return (not a) or ((not b) or c)
ans = -1
for A in range(0, 1024):
    if all(f(A, x) for x in range(0, 1024)):
        ans = A                # запоминаем последнее подходящее
print(ans)
for A in range(0, 1024):       # A неотрицательное → с 0
    if all(f(A, x)
           for x in range(0, 1024)):   # x с 0
        print(A); break

Аналитика для больших масок

  • переводим числа в двоичную запись и рассуждаем по битам;
  • x & K ≠ 0 — «у x есть общий единичный бит с K»; x & K = 0 — «общих битов нет»;
  • условие «единичные биты A лежат внутри битов числа N» собирается независимо по битам: для минимума — минимальный набор битов, для максимума — все допустимые;
  • полезно: степень двойки ⇔ x & (x-1) == 0; маска младших n бит = (1<<n) - 1.

Ловушки

  • Приоритет операций: x & 51 != 0 в Python разбирается как x & (51 != 0) — всегда пиши (x & 51) != 0.
  • Перебор x с 1 вместо 0; путаница =0 и ≠0.
05

Плоскость (x, y)

«Две переменные, метод опасной зоны и схема min/max для больших чисел»

Признаки в условии

  • две переменные x и y, неравенства / произведения / линейные формы
  • «при любых целых (не)отрицательных x и y»

Идея

Выражение зависит от двух переменных — функция f(A, x, y), а проверка идёт двойным перебором по x и y. Старт перебора зависит от условия: натуральные → с 1, неотрицательные → с 0.

Функция выражения и поиск минимума/максимума

def f(A, x, y):
    # (x·y > A) ∨ (x > y) ∨ (11 > x)
    return (x * y > A) or (x > y) or (11 > x)

ok = [A for A in range(0, 1000)
      if all(f(A, x, y)
             for x in range(0, 200)        # неотрицательные → с 0
             for y in range(0, 200))]
print(max(ok))    # наибольшее A
print(min(ok))    # наименьшее A

Аналитика для больших чисел

Каждое условие — это область на плоскости, а не только прямая:

  • линейное неравенство x + y ≤ 40 — полуплоскость (всё под/над прямой);
  • x·y ≥ A — область под/над гиперболой;
  • x² + y² ≤ R² — круг; x > A — вертикальная полуплоскость и т.д.

Выражение с ∨ истинно всюду ⇔ области-слагаемые в объединении покрывают всю плоскость. Ищем «дыру» — пересечение дополнений (где все слагаемые ложны). Обычно это ограниченная область, и её угловая (крайняя) точка даёт ответ.

Метод областей на плоскости

Пример: (x ≥ A) ∨ (y ≥ A) ∨ (x + y ≤ 40), истинно при всех натуральных x, y. Найти наибольшее A.

x y x + y = 40 (20, 20) дыра: x,y < A x + y ≤ 40 — покрыто 40 40

(x≥A) и (y≥A) закрывают верхнюю и правую полуплоскости; (x+y≤40) — нижний треугольник. Остаётся «дыра» — квадрат x, y < A. Худшая точка — угол (A−1, A−1); она обязана попасть под прямую: 2(A−1) ≤ 40 → A ≤ 21. Ответ 21.

Оптимальный перебор для больших чисел

Не перебирай 0..огромное по обеим осям — это и долго, и не влезает. Опасны только точки, где «всё остальное» ложно, а это ограниченная зона (здесь x, y < A). Перебирай именно её:

def f(A, x, y):
    return (x >= A) or (y >= A) or (x + y <= 40)

ans = 0
for A in range(1, 200):
    if all(f(A, x, y)
           for x in range(1, A)        # опасны только x < A
           for y in range(1, A)):      #          и  y < A
        ans = A
print(ans)   # 21

Для совсем больших границ идут дальше: для каждого x вычисляют граничный y аналитически (из уравнения области) и проверяют только критические точки границы — двойной перебор сводится к одинарному.

Опасная зона: прикрытие против спасателя

Тот же метод областей, но удобнее на словах. Делим условия на два вида:

  • Прикрытие — все условия без A. Где хоть одно истинно — всё выражение истинно, и значение A не важно.
  • Спасатель — условие с A. Нужен лишь там, где прикрытие отказало.

Опасная зона — точки, где всё прикрытие ложно. Задача: подобрать A так, чтобы спасатель был истинным во всей зоне. Решает крайняя (самая неудобная) точка зоны.

Спасатель с двумя координатами: схема min / max

Самый коварный случай — спасатель сравнивает A сразу с обеими координатами. Пара условий сворачивается в одно — про наибольшую координату:

(A < x) ∨ (A < y) ⟺ A < max(x, y)хотя бы одна координата больше A
(A > x) ∧ (A > y) ⟺ A > max(x, y)обе координаты меньше A

Спасатель обязан держаться во всей зоне, поэтому всё решает крайнее значение max(x,y) по зоне.

Что уменьшаем, что увеличиваем

Спасатель Равносильно По зоне ищем Ответ
(A<x) ∨ (A<y) A < max(x, y) минимум max — точка баланса x≈y наиб. A = min − 1
(A>x) ∧ (A>y) A > max(x, y) максимум max — дальняя вершина наим. A = max + 1

Одной фразой: дизъюнкция A<… → минимизируем большую координату (баланс), A на 1 меньше; конъюнкция A>… → максимизируем её (вершина), A на 1 больше.

минимумточка баланса

На прямой a·x + b·y = N (зона-линия от ≠ или граница полуплоскости) наибольшая координата минимальна при x = y:

x = y = N / (a + b)здесь max(x, y) минимален

максимумдальняя вершина

В треугольнике a·x + b·y ≤ N (зона от >) max берётся в дальней вершине — на оси:

max max(x, y) = max(N/a, N/b)бо́льший из интерсептов

Три примера с большими числами

Пример 1 — наибольшее A: (3y + 2x ≠ 500000) ∨ (A < x) ∨ (A < y), целые неотрицательные x, y.

Спасатель (A<x)∨(A<y) = A < max(x,y) → ищем минимум max. Прикрытие 3y+2x ≠ 500000 ложно на прямой 2x+3y=500000 — это зона. Баланс x=y: 5x=500000 → x=y=100000, max=100000. Нужно A < 100000 →  наибольшее A = 99999

Пример 2 — наибольшее A: (y + 4x < 850000) ∨ (A < x) ∨ (A < y), целые неотрицательные x, y.

Спасатель = A < max(x,y) → минимум max. Прикрытие 4x+y < 850000 ложно при 4x+y ≥ 850000 — зона-полуплоскость; минимум max лежит на границе 4x+y=850000. Баланс: 5x=850000 → x=y=170000. Нужно A < 170000 →  наибольшее A = 169999

Пример 3 — наименьшее A: (y + 3x > 900000) ∨ (A > x) ∧ (A > y), целые неотрицательные x, y.

Приоритет: ∧ сильнее ∨, значит спасатель — (A>x)∧(A>y) = A > max(x,y) → ищем максимум max. Прикрытие 3x+y > 900000 ложно при 3x+y ≤ 900000 — зона-треугольник. Дальняя вершина: интерсепты x=300000 (точка (300000;0)) и y=900000 (точка (0;900000)); большая — 900000. Нужно A > 900000 →  наименьшее A = 900001

Проверка кодом (умный перебор по границе)

Перебираем не всю плоскость, а только границу зоны — один цикл, O(n):

# пример 1: зона — прямая 2x + 3y = N; спасатель A < max(x, y)
N = 500000
mn = min(max(x, (N-2*x)//3)
         for x in range(0, N//2+1)
         if (N-2*x) % 3 == 0)        # только целые точки прямой
print(mn - 1)                        # 99999

Пример 2: пройти границу 4x+y=850000, взять min(max(x, 850000−4x)) → 169999. Пример 3: вершины всего две — цикл не нужен, max(300000, 900000)=900000 → 900001.

Ловушки

  • Перебор с 1 при «неотрицательных» (надо с 0); слишком узкие границы перебора для произведений x·y.
  • Неверное направление неравенства при выборе min/max A.
  • Приоритет: ∧ связывает сильнее ∨ — P ∨ Q ∧ R это P ∨ (Q ∧ R) (см. пример 3).
  • Знак ≠ делает зону не областью, а прямой — крайнюю точку ищем по её целым точкам (баланс x=y=N/(a+b)).
  • Спасатель с двумя координатами: дизъюнкция A<… → минимум (баланс), конъюнкция A>… → максимум (вершина) — не перепутай.
06

Комбинированные

«Делимость + отрезок / неравенство в одном выражении»

Признаки в условии

  • в одном выражении сочетаются ДЕЛ и x ∈ […] или ДЕЛ и линейное неравенство с A
  • вложенные импликации, иногда два параметра

Идея

Функция та же — f(A, x), просто внутри несколько проверок. Драйвер минимума/максимума не меняется.

Функция выражения

def D(n, m):   return n % m == 0
def seg(L, R, x): return L <= x <= R

def f(A, x):
    # ДЕЛ(x, A) ∨ ((x ∈ [60;80]) → ¬ДЕЛ(x, 22))
    return D(x, A) or ((not seg(60, 80, x)) or (not D(x, 22)))
for A in range(1000, 0, -1):
    if all(f(A, x)
           for x in range(1, 100000)):
        print(A); break
for A in range(1, 1000):
    if all(f(A, x)
           for x in range(1, 100000)):
        print(A); break

Аналитика для больших чисел

  • разбить на части: посчитать «известную» часть (без A) и найти x, на которых она ложна — именно там A обязан обеспечить истинность;
  • для (ДЕЛ(x,9) ∧ ДЕЛ(x,4)) → (x + A ≥ 100): посылка истинна при x, кратном НОК(9,4)=36; наименьший такой x = 36 даёт жёсткое 36 + A ≥ 100 ⇒ A ≥ 64 — проверяем все критические x;
  • комбинируем якоря: НОД/НОК (делимость) и границы (отрезки/неравенства).

Ловушки

  • Упускают одно из критических x; неверно комбинируют кванторы.
  • При двух параметрах перебор становится двумерным.
07

Редкие форматы

«Нестандартные формулировки 2024–2026»

Что сюда входит

  • Три и более отрезка с эквивалентностями: ((x∈P)≡(x∈Q)) → ((x∈R)≡(x∈Q)) с шестизначными границами — только аналитика.
  • Тождественная ЛОЖЬ вместо истины («= 0 при любом x»): инвертируем всю логику, в коде not any(...). Часто в битовых задачах.
  • Несколько параметров / два неизвестных в плоскости (A входит в несколько слагаемых) — двумерный перебор A.
  • Очень большие константы в плоскости (78125, 152, 110, 128) — графический метод обязателен.
  • Гибриды ДЕЛ + отрезок (с 2024 закрепились как отдельный жанр).
  • Квантор «существует» вместо «для всех» — в коде any(...) вместо all(...).
  • Множества по правилу (кратные / биты), маскирующиеся под тип 02, но решаемые методами типов 03/04.

Универсальный каркас под любой редкий формат

# 1) выражение → в функцию f(A, x) или f(A, x, y)
# 2) выбрать квантор: all (для всех) или any (существует)
# 3) выбрать область x: range(1,N) натуральные / range(0,N) неотрицательные / i/2 действительные
# 4) выбрать направление A: range(BIG,0,-1) максимум / range(1,BIG) минимум
def f(A, x):
    return ...    # сюда логику из условия

for A in range(1, 10**6):              # минимум; для максимума — реверс
    if all(f(A, x) for x in range(1, 100000)):
        print(A); break

Ловушки

  • Для «тождественно ложно» не перепутай all/any: ложно при любом x — это not any(f(A,x) ...).
  • При больших константах сначала аналитика (область / прямая), и только проверка — кодом-подстановкой.
Шесть типов 01–06 покрывают подавляющее большинство вариантов задания 15; редкие форматы (07) решаются тем же каркасом со сменой квантора и области x. Главный приём: вынеси выражение в f(A, x), выбери область x (натуральные range(1,N) / неотрицательные range(0,N) / действительные i/2) и направление перебора A (минимум или максимум). Для больших чисел перебор заменяется аналитикой (НОД/НОК, пересечение отрезков, биты, метод областей), а найденный A проверяется кодом-подстановкой.
Печать