Что проверяет задание
Нужно найти наименьшее, наибольшее или все значения параметра A, при которых логическое высказывание тождественно истинно. Высказывание обычно зависит от числовой переменной x, иногда от двух переменных.
Определения
Тождественная истинность, выражение истинно при всех допустимых значениях переменных. На языке логики: ∀x F(x, A) истинно.
Импликация P → Q ложна только когда P истинно, а Q ложно. Поэтому требование «импликация всегда истинна» равносильно: нет такого x, для которого P истинно и Q ложно.
Квантор всеобщности ∀x, «для каждого x». Квантор существования ∃x, «существует хотя бы один x».
Отрезок / промежуток на прямой, множество чисел между двумя границами. Для целых x это набор целых точек, для действительных, континуум; на экзамене часто достаточно мыслить отрезками на прямой.
Пересечение отрезков, числа, принадлежащие обоим. Объединение, числа, принадлежащие хотя бы одному.
Делимость как предикат, высказывание вида «x делится на k». Для параметра A это даёт арифметические ограничения, а не геометрию отрезков.
Минимальное A, среди всех A, делающих высказывание тождественно истинным, выбирают наименьшее. Аналогично для максимального. Иногда A, длина отрезка, сумма границ или само множество.
Логика отрезков
Высказывание «x ∈ A → x ∈ B» означает A ⊆ B: каждый элемент A лежит в B.
Высказывание «x ∈ A → (x ∈ B ∨ x ∈ C)» означает A ⊆ B ∪ C.
Двойственная форма с конъюнкцией в посылке описывает вложение пересечения.
Если x пробегает целые, достаточно проверить целые точки, но длина отрезка и число точек, разные величины.
Общий алгоритм решения
- Переписать условие как «выражение истинно для всех x (и y)».
- Раскрыть импликации: запретить единственный ложный случай.
- Перевести предикаты на язык отрезков, делимости или числовых неравенств.
- Получить условие на параметр A.
- Среди всех A, удовлетворяющих условию, выбрать требуемый экстремум или сумму.
- Проверить граничные точки: входят ли концы отрезков, выполняется ли равенство в делимости.
Программа построчно
В задании 15 формула должна быть тождественно истинной - верной при любых x (и y). Нужно подобрать число A. Программа делает это перебором: для каждого кандидата A проверяет формулу на множестве значений x и берёт первое A, при котором ложных случаев нет.
Разберём программу на условии:
Для какого наименьшего неотрицательного целого числа A формула ((x & 52 ≠ 0) ∧ (x & 48 = 0)) → ¬(x & A = 0) тождественно истинна при любом неотрицательном x?
def f(x, A):
return ((x & 52 != 0) and (x & 48 == 0)) <= (not (x & A == 0))
for A in range(256):
if all(f(x, A) for x in range(256)):
print(A)
break
def f(x, A): - записываем формулу как функцию: при данных x и A она возвращает True (истина) или False (ложь). Подробно - в разделе «Формула как функция».
return ((x & 52 != 0) and (x & 48 == 0)) <= (not (x & A == 0)) - сама формула, переписанная знаками Python: ∧ - and, ¬ - not, импликация → - знак <=. Подробно - в разделе «Логические операции в Python».
for A in range(256): - перебираем кандидатов A по возрастанию: 0, 1, 2, … Подробно - в разделе «Перебор A».
if all(f(x, A) for x in range(256)): - проверяем, что формула верна для всех x от 0 до 255. Подробно - в разделе «Проверка «при любом x»: all».
print(A) / break - первое подходящее A наименьшее: печатаем и останавливаемся.
Логические операции в Python
Каждый логический знак формулы заменяется так:
- ∧ (И, конъюнкция) →
and; - ∨ (ИЛИ, дизъюнкция) →
or; - ¬ (НЕ, отрицание) →
not; - → (импликация) →
<=; - ≡ (эквивалентность) →
==.
Почему импликация - это <=. Импликация a → b ложна только в одном случае: a истинно, а b ложно. В Python True - это 1, False - 0. Сравнение a <= b ложно только при a = 1 и b = 0 (1 ≤ 0 неверно), во всех остальных случаях верно. Таблица истинности совпадает с импликацией.
Скобки обязательны. Знак <= в Python «сильнее» not, and и or: запись not a <= b понимается как not (a <= b). Поэтому каждую часть импликации берут в скобки: (not a) <= (b). Скобки из условия переносят как есть.
Знаки внутри утверждений:
- ≤ →
<=, ≥ →>=, ≠ →!=, = →==(одиночное=в Python - присваивание); - умножение пишется явно: 3x →
3 * x, x² →x * x; - & - поразрядная конъюнкция, пишется так же:
x & 52.
Формула как функция
def f(x, A):
return (x & 52 != 0) <= (x & A != 0)
def f(x, A):- функция с двумя аргументами: значение переменной x и проверяемое число A. Если в формуле есть y, аргументов три:def f(x, y, A):;return …- функция возвращает значение формулы:TrueилиFalse;- функцию можно вызывать для любых x и A:
f(5, 12)- верна ли формула при x = 5 и A = 12.
Перебор A
- наименьшее A - перебираем снизу вверх и останавливаемся на первом подходящем:
for A in range(0, 1000):
if проверка:
print(A)
break
- наибольшее A - сверху вниз:
for A in range(1000, -1, -1):(третий аргумент-1- шаг вниз). Первое подходящее - наибольшее; - «сколько существует значений A» - проходим все кандидаты и считаем подходящие счётчиком
count += 1, безbreak.
Границу перебора берут с запасом: больше всех чисел формулы. Если программа печатает самую границу диапазона (например, 1000 при поиске наибольшего), значит, граница мала - её увеличивают.
Проверка «при любом x»: all
all(f(x, A) for x in range(256))
all(…) возвращает True, только если все значения внутри истинны. Запись в скобках - короткий цикл: «для каждого x от 0 до 255 вычислить f(x, A)». Как только встречается ложное значение, all сразу останавливается и возвращает False.
Это то же самое, что цикл с флажком:
ok = True
for x in range(256):
if not f(x, A):
ok = False
break
Для двух переменных перебирают все пары: all(f(x, y, A) for x in range(100) for y in range(100)).
Сколько x проверять:
- для поразрядных операций - до ближайшей степени двойки, большей всех чисел формулы: если числа меньше 256, хватает
range(256)- старшие биты x на результат не влияют; - для ДЕЛ - с запасом, в несколько раз больше наименьшего общего кратного чисел формулы;
- для координатной плоскости - чуть больше всех чисел формулы.
Делимость: DEL
def DEL(n, m):
return n % m == 0
ДЕЛ(n, m) - «n делится на m без остатка». В Python это проверка остатка: n % m - остаток от деления n на m; делится, если он равен 0. Функцию объявляют один раз над f, а в формуле пишут DEL(x, A) - так же, как в условии.
- «делится на 3» →
x % 3 == 0; - «не делится на 3» →
x % 3 != 0.
Поразрядная конъюнкция &
m & n сравнивает двоичные записи чисел разряд за разрядом и оставляет 1 только там, где единица в обоих числах:
14 = 1110
5 = 0101
14 & 5 = 0100 = 4
x & 52 != 0- у x есть хотя бы одна единица в тех разрядах, где единицы у 52;x & 48 == 0- у x нет единиц ни в одном разряде, где единицы у 48;(x & 3) == 3- у x есть все единицы числа 3 (два младших разряда).
Подсчёт перебором
Если нужно просто посчитать числа или точки с условием, перебирают их все и считают подходящие:
count = 0
for x in range(1, 51):
if x % 7 == 0 and x % 3 != 0:
count += 1
print(count)
range(1, 51)- отрезок [1; 50]: правая граница на 1 больше;- для точек на плоскости - два вложенных цикла: внешний по x, внутренний по y, каждая пара - точка.
Отрезки в Python
«x ∈ P», где P = [3; 13], - это двойное неравенство:
P = 3 <= x <= 13
Переменная P получает True, если x лежит на отрезке (концы включены), и False иначе. После этого в формуле пишут просто P, not P и т. д. Отрезок A с неизвестными концами a1 и a2 записывают так же: A = a1 <= x <= a2.
Перебор концов отрезка
def f(x, a1, a2):
P = 3 <= x <= 13
Q = 28 <= x <= 44
A = a1 <= x <= a2
return (P <= (not Q)) and (A <= P)
xs = [i / 2 for i in range(95)]
lengths = []
for a1 in range(48):
for a2 in range(a1, 48):
if all(f(x, a1, a2) for x in xs):
lengths.append(a2 - a1)
print(max(lengths))
- x в условии - любое действительное число, а не только целое. Проверяем x с шагом 0,5:
[i / 2 for i in range(95)]- это 0; 0,5; 1; …; 47. Так программа проверяет и концы отрезков, и точки между целыми числами (важно, когда концы отрезков «выколоты» отрицанием); - концы A целые: перебираем все пары a1 ≤ a2;
- каждая подходящая пара даёт длину
a2 - a1; в конце берёмmaxилиmin- что спрашивают.
Множества строк
from itertools import product
U = ["".join(w) for w in product("01", repeat=6)]
product("01", repeat=6)выдаёт все наборы из шести символов 0 и 1:('0','0','0','0','0','0'), …,('1','1','1','1','1','1')- всего 2⁶ = 64;"".join(w)склеивает набор в строку'000000';U- список всех строк, «элементов множеств».
Свойства строк:
- «чётное число единиц» →
x.count("1") % 2 == 0; - «начинается с 00» →
x.startswith("00"); - «оканчивается на 00» →
x.endswith("00").
В функции f(x) каждое множество - переменная True/False: P = x.count("1") % 2 == 0, а «x ∈ A» - проверка x in A.
Наименьшее множество A
A = set()
for x in U:
if not f(x):
A.add(x)
print(len(A))
- начинаем с пустого множества
set(); - если для строки x формула ложна, пока x не в A, - x обязана попасть в A, иначе формула не будет тождественно истинной;
A.add(x)добавляет строку в множество;len(A)- мощность (количество элементов).
Для наибольшего множества поступают наоборот: начинают с A = все элементы и удаляют те, для которых формула ложна (A.remove(x)).
