P
pro·school.ru
Каталог школ
🧠 ВсОШ · Заключительный этап · 2025/2026

Олимпиада по искусственному интеллекту 9–11 классызаключительный этап ВсОШ 2025/2026: задания и ответы

Официальный комплект заключительного этапа Всероссийской олимпиады школьников по искусственному интеллекту для 9–11 классов (2025/2026 учебный год). Задания и решения с критериями оценивания — скачайте PDF или прорешайте онлайн по тексту ниже.

Просмотр PDF: Задания — 1 турОткрыть в новой вкладке ↗

Задания — текст для прорешивания

Текст извлечён из официального PDF автоматически: формулы, таблицы и рисунки могут отображаться неточно — сверяйтесь с документом выше.

Задания — 1 тур

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Первый тур. Москва, 23 марта 2026 г.

A. Прямая снова крутится Рассмотрим линейный классификатор на плоскости R2 , задаваемый прямой x + Ay = 0. Точка (x, y) относится к первому классу, если x + Ay > 0. В противном случае, если x + Ay ⩽ 0, точка относится ко второму классу. Приведите пример таких целых чисел A, b и c, что для трёхчлена P (x) = x2 + bx + c точка (2, P (2)) относится к первому классу, а точки (1, P (1)) и (3, P (3)) — ко второму классу.

B. Генератор случайности Генератор случайных чисел работает следующим образом. Если задать ему натуральное число m ⩾ 6, он выдаст последовательно 10 натуральных чисел. Каждое число выбирается им независимо и равновероятно из множества {1, 2, . . . , m}. В силу системной ошибки, результат генератора выводится на экран следующим образом: числа 1, 2, 3, 4, 5 выводятся без изменений, а вместо любого другого числа выводится число 6. При каком натуральном m ⩾ 6 вероятность получить на экране последовательность чисел 6, 2, 6, 6, 1, 6, 4, 6, 3, 5 максимальна?

C. Градиентный спуск на листочке Рассмотрим функцию f (x, y) = x20 + y 26 . Ниже приведён алгоритм, который по начальной точке (a, b) строит последовательность точек (Xi , Yi ), i = 0, 1, 2, . . . Ниже в коде decrease_lr = False для пункта (a), и decrease_lr = True для пункта (b). X0 , Y0 ← a, b dx, dy ← 0, 1 lr ← 1 for i = 1, 2, 3, . . . do dx, dy ← dy, -dx if decrease_lr and i · lr ⩾ 2 then: lr ← lr / 2 end if Xi , Yi ← Xi-1 , Yi-1 if f(Xi + dx · lr, Yi + dy · lr) < f(Xi , Yi ) then Xi ← Xi + dx · lr Yi ← Yi + dy · lr end if end for Докажите, что, какая бы точка ни была начальной, справедливы следующие утверждения. (a) Hайдётся такой номер N , что расстояние от точки (XN , YN ) до начала координат не превосходит 1. (b) Hайдётся такой номер N0 , что для любого номера N ⩾ N0 расстояние от точки 1 (XN , YN ) до начала координат не превосходит 1000 .

Страница 1 из 4

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Первый тур. Москва, 23 марта 2026 г.

D. Лазер Пол прямоугольной камеры с зеркальными стенами имеет форму клетчатого прямоугольника 1000 × 3000. В узлах сетки расположены датчики температуры (всего 1001 · 3001 датчиков). Изначально все датчики показывают температуру 0◦ C. В центрах некоторых клеток расположено по одному устройству, испускающему лазерные лучи. Каждое устройство испускает лазерный луч по диагонали клетки, в центре которой оно находится (то есть в одном из четырех возможных направлений, в сторону одного из узлов соответствующей клетки). При этом то же устройство поглощает лучи, приходящие из противоположного узла. На лучи трёх других направлений устройство не влияет. Всего установлено 300 устройств, которые излучают по одному лучу. Каждый луч отражается зеркально от стенок (попадая в угол, луч отражается в противоположном направлении), пока не поглотится каким-то из устройств. Лазерный луч, «посещая» очередной датчик температуры, увеличивает его показание на 1◦ C. Если же в точке, где располагается датчик, луч отражается, он увеличивает показание соответствующего датчика сразу на 2◦ C. Назовём тепловой картой матрицу A размера 1001 × 3001, элементы которой равны итоговым показаниям соответствующих датчиков температуры. Для проверки корректности показаний датчиков, полученные значения анализируют следующим образом. Выбирается квадратная матрица K размера s×s, называемая ядром размера s, после чего вычисляется свёртка матрицы A с ядром K, то есть матрица B = A⋆K размера (1002 − s) × (3002 − s), где

B[i][j] =

s−1 X s−1 X

A[i + u][j + v]K[u][v],

0 ⩽ i ⩽ 1001 − s,

0 ⩽ j ⩽ 3001 − s.

u=0 v=0

Назовём ядро K определяющим, если для любой тепловой карты A все элементы матрицы B равны нулю, при этом не все элементы ядра K равны 0. При каком наименьшем значении s существует определяющее ядро размера s? Ниже на рисунке вы можете видеть как запущенный луч изменит температуру в датчиках до момента поглощения.

Страница 2 из 4

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Первый тур. Москва, 23 марта 2026 г.

E. Новая выборка Модель по вещественному числу x предсказывает вещественное число методом линейной регрессии, то есть по формуле f (x) = ax + b, где a, b ∈ R — параметры модели f . Дана обучающая выборка (x1 , y1 ), (x2 , y2 ), . . . (xn , yn ), где x1 , . . . , xn , y1 , . . . yn ∈ R. Модель f обучается на этих данных: параметры a и b выбираются методом наименьших квадратов, то есть так, чтобы значение выражения n X

(yi − f (xi ))2

i=1

было минимальным. Определим коэффициент детерминации R02 , посчитанный на этой выборке, следующим образом: n P

(yi − f (xi ))2

R02 = 1 − i=1 n P

, (yi

− ȳ)2

1X yi . где ȳ = n i=1

i=1

Построим новую выборку, добавив к исходной n объектов (xi , f (xi )), i = 1, 2, . . . , n. Пусть на получившейся выборке из 2n объектов обучается модель g(x) = cx + d также методом наименьших квадратов. Обозначим через R12 коэффициент детерминации для модели g, посчитанный на выборке из всех 2n объектов. Предполагается, что параметры обеих моделей удалось подобрать методом наименьших квадратов единственным образом. Выразите R12 через R02 .

Страница 3 из 4

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Первый тур. Москва, 23 марта 2026 г.

F. Вымышленная ситуация Артур и Таня готовят вычислительные мощности к практическому туру для 1000 участников финала всероссийской олимпиады школьников по ИИ. Изначально они хотели развернуть всю инфраструктуру в облаке и выдавать видеокарты (GPU) из общего набора, но, чтобы исключить сетевые задержки, было решено собрать каждому участнику персональный кластер из 8 устройств. На складе партнёров олимпиады есть по 8000 устройств каждого из трех типов. • Тип 1 (зеленые) — CUDA, быстрые, но горячие. • Тип 2 (красные) — поддерживают открытые стандарты, подходят для обучения больших языковых моделей (LLM), но требуют сложной настройки драйверов. • Тип 3 (желтые) — экспериментальные ускорители (TPU). По условиям поставки устройства типа 1 можно получать только партиями по 7 штук; устройств каждого типа должно быть не меньше, чем 2026; всего со склада следует взять ровно 8000 устройств. Если в соответствии с этими условиями можно привезти со склада y1 устройств первого типа, y2 — второго и y3 — третьего, будем называть тройку целых чисел (y1 , y2 , y3 ) допустимой, то есть: y1 + y2 + y3 = 8000,

yt ⩾ 2026 (t = 1, 2, 3),

. y1 .. 7.

Каждый участник имеет персональный шифр i = 1, 2, . . . , 1000. Устройства, поставленные со склада, распределяются между участниками. Пусть участник i получает персональный кластер Si = (xi,1 , xi,2 , xi,3 ), в котором xi,1 , xi,2 , xi,3 — количество устройств первого, второго и третьего типа соответственно. Числа xi,1 , xi,2 , xi,3 — целые неотрицательные, причём xi,1 + xi,2 + xi,3 = 8. Оказалось, что кто-то из участников предпочитает «зеленых», кто-то фанат открытых стандартов и выбирает «красных», а кто-то любит необычных «жёлтых». Для каждого участника заданы предпочтения устройств ui,1 , ui,2 , ui,3 , указанные во входном файле gpu.csv. Файл содержит заголовок и 1000 строк с 3 столбцами с данными: в i-й строке записаны три целых числа ui,1 , ui,2 , ui,3 (именно в таком порядке), соответствующие предпочтениям участника i. Полезность кластера S = (x1 , x2 , x3 ) для участника i задаётся формулой Vi (S) = ui,1 x1 + ui,2 x2 + ui,3 x3 . Распределение устройств называется честным, если неравенство Vi (Si ) ⩾ Vi (Sj ) справедливо для любых двух участников i, j. (a) Приведите пример допустимой тройки значений (y1 , y2 , y3 ), при которой честного распределения не существует, и докажите это. (b) Существует ли допустимая тройка (y1 , y2 , y3 ), при которой существует честное распределение?

Страница 4 из 4

Задания — 2 тур

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

A. Что посмотреть? Условие В онлайн-кинотеатре пользователю нужно рекомендовать один фильм из нескольких возможных вариантов. Для этого система подбирает для пользователя пять фильмов-кандидатов и задаёт правило, по которому нужно выбрать один из них. Такую задачу выбора будем называть запросом. Каждый запрос описывается следующими данными: • user_id — идентификатор пользователя; • query_type — тип запроса, то есть правило выбора; • c1 , c2 , c3 , c4 , c5 — идентификаторы пяти различных фильмов-кандидатов. Каждая строка файла queries_A.csv задаёт один запрос: для указанного пользователя из пяти предложенных фильмов нужно выбрать один. Для каждого запроса нужно вывести item_id выбранного фильма.

Формат ввода Используйте приложенные к задаче файлы: 1. queries_A.csv 2. items_A.csv 3. events_A.csv 4. item_meta_A.json 5. sessions_A.json

Примечания К задаче приложен файл baseline_A.ipynb с примером чтения входных файлов, фильтрацией запросов по типу для каждой подзадачи и подробным описанием данных.

Формат вывода Нужно вывести CSV-файл с заголовком query_id,item_id Для каждого query_id, относящегося к соответствующей подзадаче, в ответе должна быть ровно одна строка. Лишних строк быть не должно.

Система оценивания Максимальный балл за каждую подзадачу — 20. Решение считается верным, если для каждого query_id, относящегося к соответствующей подзадаче, указан правильный item_id по правилам задачи. Итоговые баллы за задачу выставляются по лучшей посылке.

Страница 1

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

A1. Подзадача 1 При решении этой подзадачи следует отфильтровать строки queries_A.csv, оставив только запросы типа favorite_genre, и вывести ответы только для них. Для запроса favorite_genre для каждого из пяти кандидатов берётся его жанр, после чего для этого жанра вычисляется сумма весов всех событий данного пользователя по фильмам того же жанра, где • open = 1 • finish = 2 • like = 3 Выбирается кандидат, для жанра которого эта сумма максимальна. При равенстве: 1. фильм с меньшей duration; 2. фильм с меньшим item_id.

A2. Подзадача 2 При решении этой подзадачи следует отфильтровать строки queries.csv, оставив только запросы типа actor_match, и вывести ответы только для них. Для каждого запроса actor_match формируется множество watched_actors актёров фильмов, которые пользователь 1. Или посмотрел — finish; 2. Или отметил как понравившиеся — like. Рассматриваются только актёры из поля actors файла item_meta_A.json. Если поле actors отсутствует, его нужно считать пустым списком. Для кандидата score равен числу общих элементов множеств: множества актёров этого кандидата; множества watched_actors. При равенстве выбирается фильм с минимальным item_id.

A3. Подзадача 3 При решении этой подзадачи следует отфильтровать строки queries_A.csv, оставив только запросы типа next_in_session, и вывести ответы только для них. Для пользователя рассмотрим все соседние пары фильмов f1 → f2 , извлечённые из всех списков path в файле sessions_A.json. Если path = [x1 , x2 , . . . , xk ], то из него извлекаются пары:

Страница 2

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

x1 → x2 x2 → x3 .. . xk−1 → xk

Для кандидата f2 его score — это количество таких пар f1 → f2 , где фильм f1 пользователь когда-либо досмотрел, то есть для него есть событие finish в файле events_A.csv. Каждое вхождение пары в sessions_A.json учитывается отдельно. Если у пользователя нет подходящих пар, то score всех кандидатов считается равным 0. Выбирается кандидат с максимальным score. При равенстве выбирается фильм с минимальным item_id.

Страница

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

B. Сейсмоактивный остров Условие Исследователи из центра сейсмологического мониторинга анализируют записи землетрясений на острове Террамотус. Для каждого события данные собирались на двух станциях наблюдения, расположенных в разных концах острова. На каждой станции установлены два измерительных прибора, размещённые в разных частях станции. Данные передавались со станции в лабораторию по двум каналам: по оптоволоконному кабелю и по беспроводной связи. Но в беспроводной связи были помехи, и некоторые моменты времени в данных были утеряны. Каждый прибор фиксирует 10 величин каждую минуту в течении 100 минут. Чтобы ускорить разбор архива, исследователи привлекли ИИ-агента. Исследователи неаккуратно написали запрос и дали агенту слишком много прав. Затем, в процессе работы агент зачем-то переименовал все файлы. После этого исследователям стало непонятно, какие записи относятся к одному и тому же землетрясению: файлы перемешаны, а исходная группировка утрачена. Исследователи запаниковали, но затем взяли себя в руки и обратились за помощью к школьнику Олегу — эксперту в машинном обучении. Помогите ему кластеризовать данные и починить последствия неконтролируемых экспериментов с ИИ. Известно, что каждому землетрясению соответствуют ровно 8 наблюдений: по 2 прибора на каждой из 2 станций и по 2 способа передачи. Вам нужно понять, какие наблюдения относятся к одному и тому же землетрясению.

Формат ввода К задаче прикреплены файлы: • data_B.npy, содержащий массив наблюдений 240 × 10 × 100 — 240 наблюдений по 10 значений в течение 100 минут. • baseline_B.ipynb — ноутбук с базовым решением задачи. • submission_B.csv — пример решения, которое нужно отправить в тестирующую систему.

Формат вывода Для проверки необходимо загрузить архив solution_B.zip. Архив должен содержать: 1. Файл submission_B.csv с двумя колонками: • ID — номер наблюдения в data_B.npy; • target — предсказанное значение целевой переменной. 2. Файл solution_B.ipynb — Jupyter Notebook с вашим решением. Допускается добавлять в архив дополнительные файлы, необходимые для работы решения. При этом архив должен содержать ровно один файл с расширением .csv и ровно один файл с расширением .ipynb.

Система оценивания Страница 4

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г. За эту задачу можно получить до 60 баллов. Данные разбиты на публичную и приватную части. Когда вы отправляете submission_B.csv, вам показывается результат на публичной части. После завершения этапа результат будет пересчитан на приватной части. Публичная и приватная часть не пересекаются. После окончания этапа ваша метрика будет приведена к 60-балльной шкале по следующему правилу: • результат baseline-решения со значением ARI ⩽ X оценивается в 0 баллов; • результат со значением ARI ⩾ Y оценивается в 60 баллов; • если значение ARI находится между X и Y , количество баллов вычисляется по формуле линейной интерполяции: Score = 60 ·

ARI − X . Y −X

Значения метрик X и Y будут доступны в тестирующей системе. Итоговые баллы за задачу выставляются по последней посылке.

Метрика оценивания точности ответа В этой задаче используется метрика ARI (Adjusted Rand Index). Чем большее количество пар объектов участник правильно распределяет по кластерам (например, если оба объекта находятся в разных кластерах и участник также относит их к разным кластерам, ИЛИ оба объекта находятся в одном кластере и участник тоже относит их к одному кластеру), тем выше эта метрика. ARI принимает значение 0 для случайного разбиения на кластеры, значение 1 для идеально правильного разбиения и может принимать отрицательные значения в случае разбиения хуже случайного. Пример расчета метрики ARI на ‘Python‘: from sklearn . metrics import adjusted_rand_score labels_true = [0 , 0 , 1 , 1 , 2 , 2] labels_pred = [1 , 1 , 0 , 0 , 2 , 2] ari = adjusted_rand_score ( labels_true , labels_pred ) print ( " ARI = " , ari )

Страница

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

C. Ошибка новичка Условие Машу позвали на летнюю стажировку в научный институт, который занимается исследованием ареалов обитания животных. В качестве примера интересного задания, с которым Маше предстоит работать, исследователи поделились наблюдениями за местами обитания нового вида ленивцев (Bradypus procrastinator shkolnikus). Совсем недавно этот вид был выделен из уже известного ранее. Теперь учёные хотят обнаружить и другие потенциальные зоны его обитания. Для удобства Маши данные были разбиты на train и test. Поскольку у исследователей есть только собственные данные о локациях, где уже живут ленивцы, они попросили статистическое агентство Южной Америки предоставить данные и по другим локациям, чтобы расширить тестовый датасет. Каждая строка в данных описывает некоторую локацию и содержит измеренные параметры, характеризующие климат, погоду, растительный и животный мир. Работники института пожаловались, что у них никак не получается построить хорошую модель для этой задачи. Маша посмотрела на данные и сразу заметила две типичные ошибки новичка, допущенные сотрудниками института. Помогите Маше построить модель, которая по имеющимся данным предсказывает, относится ли локация к потенциальной зоне обитания нового вида ленивцев.

Формат ввода К задаче прикреплены файлы: • map_C.json — файл следующей структуры: { " coastline ": [...] }

coastline — массив координат береговой линии, используемый для визуализации. • train_C.csv — обучающая выборка. Каждый объект содержит признаки локации и целевую переменную target (бинарная метка 0 или 1); • test_C.csv — тестовая выборка. Каждый объект содержит идентификатор id и признаки локации; • baseline_C.ipynb — ноутбук с базовым решением задачи; • submission_C.csv — пример решения, которое нужно отправить в тестирующую систему.

Формат вывода Для проверки необходимо загрузить архив solution_C.zip. Архив должен содержать: 1. Файл submission_C.csv с двумя колонками: • ID — идентификатор объекта из тестовой выборки test; • target — предсказанная бинарная метка класса (0 или 1). 2. Файл solution_C.ipynb — Jupyter Notebook с вашим решением. Страница

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г. Допускается добавлять в архив дополнительные файлы, необходимые для работы решения. При этом архив должен содержать ровно один файл с расширением .csv и ровно один файл с расширением .ipynb.

Система оценивания За эту задачу можно получить до 60 баллов. Данные разбиты на публичную и приватную части. Когда вы отправляете submission_C.csv, вам показывается результат на публичной части. После завершения этапа результат будет пересчитан на приватной части. Публичная и приватная часть не пересекаются. После окончания этапа ваша метрика будет приведена к 60-балльной шкале по следующему правилу: • результат baseline-решения со значением F1 ⩽ X оценивается в 0 баллов; • результат со значением F1 ⩾ Y оценивается в 60 баллов; • если значение F1 находится между X и Y , количество баллов вычисляется по формуле линейной интерполяции: Score = 60 ·

F1 − X . Y −X

Значения метрик X и Y будут доступны в тестирующей системе. Итоговые баллы за задачу выставляются по последней посылке.

Метрика оценивания точности ответа В этой задаче используется метрика F1-score. Участник отправляет бинарные метки класса (0 или 1). Обозначим: • TP — количество истинно положительных предсказаний; • FP — количество ложно положительных предсказаний; • FN — количество ложно отрицательных предсказаний. Тогда

Precision =

Recall = а F1 =

TP , TP + FP

TP , TP + FN

2 · Precision · Recall . Precision + Recall

Значение F1-score лежит в диапазоне от 0 до 1. Чем выше значение, тем лучше качество классификации. Пример расчёта метрики F1-score на Python: from sklearn . metrics import f1_score score = f1_score ( y_true , y_pred ) print ( " F1 - score = " , score )

Страница 7

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

D. Наследие Человечества Условие Извержение вулкана Везувий в 79 году нашей эры засыпало пеплом и уничтожило город Помпеи и многие другие города, в том числе Геркуланум, где в 1750 году была обнаружена древнеримская вилла с большим количеством античных папирусов. Свитки, хранившиеся в библиотеке, обуглились и превратились в хрупкие чёрные цилиндры. В наши дни учёные стремятся прочитать их, используя методы рентгеновской томографии, компьютерной реконструкции и машинного обучения. Когда Вадим узнал об этом, он подумал, что тоже хотел бы поучаствовать в процессе расшифровки. Узнав, что учёные уже решают задачу восстановления текста внутри каждого отдельного папируса, он понял, что следующий шаг — восстановить правильный порядок самих папирусов, ведь античные трактаты обычно состояли из нескольких частей. Поскольку Вадим не знает древнегреческого, он решил сначала рассмотреть аналогичную задачу на русском языке. Для этого он взял сборник стихотворений, часть стихотворений оставил неизменной (train_D.json). Каждое из оставшихся стихотворений (test_D.json) он разделил на две части, а затем перемешал между собой все левые и правые части. Теперь задача Вадима — восстановить исходные пары и собрать стихотворения обратно.

Формат ввода К задаче прикреплены следующие файлы: • model_D.zip — веса для большой языковой модели. • baseline_D.ipynb — ноутбук с базовым решением задачи и примером использования большой языковой модели model_D.zip для получения эмбедингов текста • train_D.json — дополнительные стихотворения целиком; • test_D.json — перемешанные левые и правые части стихотворений; • submission_D.csv — пример решения, которое необходимо отправить в тестирующую систему.

Формат вывода Для проверки необходимо загрузить архив solution_D.zip. Архив должен содержать: 1. Файл submission_D.csv с двумя колонками: • left_id — ID левой половины из test_D.json; • right_id — ID правой половины из test_D.json. 2. Файл solution_D.ipynb — Jupyter Notebook с вашим решением. Допускается добавлять в архив дополнительные файлы, необходимые для работы решения. При этом архив должен содержать ровно один файл с расширением .csv и ровно один файл с расширением .ipynb.

Система оценивания За эту задачу можно получить до 60 баллов. Страница 8

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г. Данные разбиты на публичную и приватную части. Когда вы отправляете submission_D.csv, вам показывается результат на публичной части. После завершения этапа результат будет пересчитан на приватной части. Публичная и приватная часть не пересекаются. После окончания этапа ваша метрика будет приведена к 60-балльной шкале по следующему правилу: • результат baseline-решения со значением Accuracy ⩽ X оценивается в 0 баллов; • результат со значением Accuracy ⩾ Y оценивается в 60 баллов; • если значение Accuracy находится между X и Y , количество баллов вычисляется по формуле линейной интерполяции: Score = 60 ·

Accuracy − X . Y −X

Значения метрик X и Y будут доступны в тестирующей системе. Итоговые баллы за задачу выставляются по последней посылке.

Метрика оценивания точности ответа В этой задаче оценивается корректность сопоставления левых и правых частей стихотворений. Пусть: • N — общее количество левых частей в test.json; • K — количество правильно восстановленных пар, то есть таких пар (left_id, right_id), которые совпадают с истинным соответствием. Тогда метрика вычисляется по формуле Accuracy =

K . N

Иными словами, метрика показывает долю правильно восстановленных стихотворений. Значение Accuracy лежит в диапазоне от 0 до 1. Чем выше значение, тем лучше качество решения. Пример расчёта Accuracy на Python: import pandas as pd merged = true_pairs . merge ( pred_pairs , on =" left_id " , suffixes =( " _true " , " _pred ") ) correct = ( merged [" right_id_true "] == merged [" right_id_pred " ]) . sum () accuracy = correct / len ( merged ) print ( " Accuracy = " , accuracy )

Страница 9

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

E. Подозрительные пирожные Условие Саша часто болеет и пытается понять причину. Он подозревает, что проблема может быть связана с пирожными, которые он регулярно ест. Недавно Саша купил новую партию пирожных, и среди них могли оказаться необычные, которые для него вредны. Саша очень хочет съесть все пирожные, но не хочет снова заболеть. Для этого Саше нужно научиться определять, какие пирожные отличаются от остальных. Саша уверен, что подозрительные пирожные можно отличить визуально. Чтобы научиться их находить, он решил обучить свёрточную нейросеть анализировать изображения пирожных. Однако для обучения у Саши есть только «нормальные» пирожные, про которые известно, что они ему точно не вредны. Всего Саша ест 10 видов пирожных, поэтому он обучил нейросеть классифицировать «нормальные» пирожные на 10 классов. Теперь Саше нужно придумать, как с помощью этой модели находить «вредные» пирожные. Саша просит вашей помощи, чтобы найти подозрительные пирожные среди новой партии. Вам даны изображения пирожных из новой партии и веса предобученной нейросети. Нейросеть обучена только на «нормальных» пирожных и решает задачу классификации на 10 классов. Вам нужно определить, какие пирожные из новой партии отличаются от «нормальных». Известно, что подозрительных пирожных ровно 1000.

Формат ввода К задаче прикреплены файлы: • public_test_package_E.npz — тестовые изображения размера 32 × 32; • model_weights_E.pt — веса предобученной CNN; • baseline_E.ipynb — базовый ноутбук с примером решения; • submission_E.csv — пример файла ответа.

Формат вывода Для проверки необходимо загрузить архив solution_E.zip. Архив должен содержать: 1. Файл submission_E.csv с двумя колонками: • id — индекс объекта от 0 до N − 1; • is_outlier — ваш прогноз: – 1, если объект является подозрительным; – 0, если объект считается нормальным. 2. Файл solution_E.ipynb — Jupyter Notebook с вашим решением. В файле submission_E.csv должно быть ровно 1000 строк со значением is_outlier = 1. Допускается добавлять в архив дополнительные файлы, необходимые для работы решения. При этом архив должен содержать ровно один файл с расширением .csv и ровно один файл с расширением .ipynb. Страница 10

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

Система оценивания За эту задачу можно получить до 60 баллов. Если в отправленном файле количество объектов, помеченных как подозрительные, отличается от 1000, то за задачу выставляется 0 баллов. Данные разбиты на публичную и приватную части. Когда вы отправляете submission_E.csv, вам показывается результат на публичной части. После завершения этапа результат будет пересчитан на приватной части. Публичная и приватная часть не пересекаются. После окончания этапа значение метрики будет приведено к 60-балльной шкале по следующему правилу: • результат baseline-решения со значением Recall@K ⩽ X оценивается в 0 баллов; • результат со значением Recall@K ⩾ Y оценивается в 60 баллов; • если значение Recall@K находится между X и Y , количество баллов вычисляется по формуле линейной интерполяции: Score = 60 ·

Recall@K − X . Y −X

Значения метрик X и Y будут доступны в тестирующей системе. Итоговые баллы за задачу выставляются по последней посылке.

Метрика оценивания точности ответа В этой задаче используется метрика Recall@K. Участник должен отметить ровно K = 1000 объектов как подозрительные. Пусть hits@K — число правильно найденных подозрительных объектов среди отмеченных. Тогда Recall@K =

hits@K . K

Иными словами, чем больше настоящих подозрительных объектов вы найдёте среди выбранных 1000, тем выше результат.

Страница 11

Ответы и решения — показать

Официальные ответы и критерии оценивания жюри. Сначала решите задания самостоятельно.

Решения — 1 тур

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Первый тур. Москва, 23 марта 2026 г.

A. Прямая снова крутится Рассмотрим линейный классификатор на плоскости R2 , задаваемый прямой x + Ay = 0. Точка (x, y) относится к первому классу, если x + Ay > 0. В противном случае, если x + Ay ⩽ 0, точка относится ко второму классу. Приведите пример таких целых чисел A, b и c, что для трёхчлена P (x) = x 2 + bx + c точка (2, P (2)) относится к первому классу, а точки (1, P (1)) и (3, P (3)) — ко второму классу.

Решение. Один из примеров: A = −4, b = −4, c = 4, то есть P (x) = (x − 2)2 = x2 − 4x + 4. Положим L(x, y) = x − 4y. Тогда L(1, P (1)) = L(1, 1) = −3 ⩽ 0 =⇒ точка (1, P (1)) относится ко второму классу; L(2, P (2)) = L(2, 0) = 2 > 0 =⇒ точка (2, P (2)) относится к первому классу; L(3, P (3)) = L(3, 1) = −1 ⩽ 0 =⇒ точка (3, P (3)) относится ко второму классу.

Страница 1 из 11

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Первый тур. Москва, 23 марта 2026 г.

B. Генератор случайности Генератор случайных чисел работает следующим образом. Если задать ему натуральное число m ⩾ 6, он выдаст последовательно 10 натуральных чисел. Каждое число выбирается им независимо и равновероятно из множества {1, 2, . . . , m}. В силу системной ошибки, результат генератора выводится на экран следующим образом: числа 1, 2, 3, 4, 5 выводятся без изменений, а вместо любого другого числа выводится число 6. При каком натуральном m ⩾ 6 вероятность получить на экране последовательность чисел 6, 2, 6, 6, 1, 6, 4, 6, 3, 5 максимальна?

Решение. Ответ: m = 10. Вероятность того, что на данном месте в последовательности будет число k ∈ {1, 2, 3, 4, 5} составляет 1 P(k) = . m В связи с тем, что все остальные числа заменены на 6, та же вероятность для числа 6 составляет m−5 . P(6) = m В изучаемой последовательности значение 6 встретилось 5 раз, и ещё 5 раз встретились числа 1, . . . , 5. Поэтому вероятность получить эту последовательность равна  5  5  5 1 m−5 m−5 = . m m m2 Максимальное значение этой вероятности достигается, когда наибольшее возможное значение принимает величина 1 5 m−5 = − 2. 2 m m m Положим x = m1 . Поскольку максимум квадратичной функции f (x) = x − 5x2 достигается 1 при x = 10 , искомое значение m = 10.

Страница 2 из 11

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Первый тур. Москва, 23 марта 2026 г.

C. Градиентный спуск на листочке Рассмотрим функцию f (x, y) = x20 + y 26 . Ниже приведён алгоритм, который по начальной точке (a, b) строит последовательность точек (Xi , Yi ), i = 0, 1, 2, . . . Ниже в коде decrease_lr = False для пункта (a), и decrease_lr = True для пункта (b). X0 , Y0 ← a, b dx, dy ← 0, 1 lr ← 1 for i = 1, 2, 3, . . . do dx, dy ← dy, -dx if decrease_lr and i · lr ⩾ 2 then: lr ← lr / 2 end if Xi , Yi ← Xi-1 , Yi-1 if f(Xi + dx · lr, Yi + dy · lr) < f(Xi , Yi ) then Xi ← Xi + dx · lr Yi ← Yi + dy · lr end if end for Докажите, что, какая бы точка ни была начальной, справедливы следующие утверждения. (a) Hайдётся такой номер N , что расстояние от точки (XN , YN ) до начала координат не превосходит 1. (b) Hайдётся такой номер N0 , что для любого номера N ⩾ N0 расстояние от точки 1 (XN , YN ) до начала координат не превосходит 1000 .

Решение. Изучим очередной шаг алгоритма. Делается попытка изменить значение одной из координат на некоторую величину lr. Это изменение происходит в том и только в том случае, когда уменьшается модуль соответствующей координаты, поскольку f (x + lr, y) < f (x, y) ⇐⇒ |x + lr| < |x|, f (x, y + lr) < f (x, y) ⇐⇒ |y + lr| < |y|. Пункт (a). По координате x алгоритм делает попытки изменения +1, −1, +1, −1, . . . , по координате y, наоборот, −1, +1, −1, +1, . . . . Рассмотрим две последовательные попытки изменения x-координаты. • Если |x| > 21 , то ровно одна из двух попыток приводит к изменению координаты, при этом |x| уменьшается ровно на 1. • Если |x| ⩽ 12 , то ни одна из попыток к изменению координаты не приводит. Таким образом, после конечного числа таких пар шагов алгоритма будет выполнено неравенство |x| ⩽ 21 , так как |x| не может бесконечное количество раз уменьшаться на 1. То же верно и для y-координаты, значит, для некоторого номера N будут выполнены неравенства 1 1 |XN | ⩽ , |YN | ⩽ . 2 2 Страница 3 из 11

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Первый тур. Москва, 23 марта 2026 г.

Тогда и расстояние до начала координат в этого момент будет меньше, чем 1: XN2 + YN2 ⩽

1 1 1 + = < 1. 4 4 2

В силу сказанного выше, это неравенство будет справедливо и для любого номера M ⩾ N , это соображение нам потребуется для решения другого пункта. Пункт (b). Разделим шаги алгоритма на блоки последовательных с одним и тем же значением величины lr. Рассмотрим m-й блок, состоящий из шагов с номерами i = 2m + 1, . . . , 2m+1 , где lr = 21m . Далее изучаем блоки для m ⩾ 3. Тогда аналогично пункту а) внутри каждого блока каждая вторая попытка изменения 1 x-координаты уменьшаeт значение |x|, пока выполняется неравенство |x| > lr2 = 2m+1 . Значит, для m-го блока шагов справедливо хотя бы одно из следующих утверждений. lr . 2 2. В результате каждого четвёртого шага алгоритма (то есть каждой второй попытки изменения x-координаты) величина |x| уменьшалась на lr. Таким образом, за этот блок шагов она суммарно уменьшилась на 2m−2 · lr = 41 .

1. После выполнения всех шагов m-го блока |x| ⩽

Заметим, что случай 2 мог возникнуть лишь для конечного числа блоков, так как |x| не может уменьшаться бесконечное количество раз на 14 . Следовательно, существует такое число Mx , что для блоков с номерам, большими Mx , справедливо первое утверждение. Итого при N > Nx = max(2Mx +1 , 10) будут выполнены неравенства |XN | ⩽

1 2Nx +1

1 1 < . 11 2 2000

Аналогично и для координаты y найдётся такой номер Ny , что при N > Ny выполнено 1 неравенство |YN | < . 2000 Тогда при N > max(Nx , Ny ) имеют место неравенства |XN | <

1 1 и |YN | < . Значит, 2000 2000

p p |XN |2 + |YN |2 ⩽ |XN |2 + 2 · |XN ||YN | + |Y |2 = |XN | + |YN | <

1 1 1 + = . 2000 2000 1000

Таким образом, номер N0 = max(Nx , Ny ) удовлетворяет условиям задачи. Замечание. Из приведённого решения следует, что последовательность точек (Xi , Yi ) сходится к точке минимума функции f (x, y) — началу координат.

Страница 4 из 11

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Первый тур. Москва, 23 марта 2026 г.

D. Лазер Пол прямоугольной камеры с зеркальными стенами имеет форму клетчатого прямоугольника 1000 × 3000. В узлах сетки расположены датчики температуры (всего 1001 · 3001 датчиков). Изначально все датчики показывают температуру 0◦ C. В центрах некоторых клеток расположено по одному устройству, испускающему лазерные лучи. Каждое устройство испускает лазерный луч по диагонали клетки, в центре которой оно находится (то есть в одном из четырех возможных направлений, в сторону одного из узлов соответствующей клетки). При этом то же устройство поглощает лучи, приходящие из противоположного узла. На лучи трёх других направлений устройство не влияет. Всего установлено 300 устройств, которые излучают по одному лучу. Каждый луч отражается зеркально от стенок (попадая в угол, луч отражается в противоположном направлении), пока не поглотится каким-то из устройств. Лазерный луч, «посещая» очередной датчик температуры, увеличивает его показание на 1◦ C. Если же в точке, где располагается датчик, луч отражается, он увеличивает показание соответствующего датчика сразу на 2◦ C. Назовём тепловой картой матрицу A размера 1001 × 3001, элементы которой равны итоговым показаниям соответствующих датчиков температуры. Для проверки корректности показаний датчиков, полученные значения анализируют следующим образом. Выбирается квадратная матрица K размера s×s, называемая ядром размера s, после чего вычисляется свёртка матрицы A с ядром K, то есть матрица B = A⋆K размера (1002 − s) × (3002 − s), где

B[i][j] =

s−1 X s−1 X

A[i + u][j + v]K[u][v],

0 ⩽ i ⩽ 1001 − s,

0 ⩽ j ⩽ 3001 − s.

u=0 v=0

Назовём ядро K определяющим, если для любой тепловой карты A все элементы матрицы B равны нулю, при этом не все элементы ядра K равны 0. При каком наименьшем значении s существует определяющее ядро размера s? Ниже на рисунке вы можете видеть как запущенный луч изменит температуру в датчиках до момента поглощения.

Решение. Ответ: s = 3. Рассмотрим ядро

 0 1 0 K = −1 0 −1 . 0 1 0 

Страница 5 из 11

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Первый тур. Москва, 23 марта 2026 г.

Покажем, что оно подходит. Заметим, что траектории всех лучей разбиваются на циклы потому что каждый датчик принимает и испускает ровно один луч (отметим, что один цикл может быть объединением траекторий сразу нескольких лучей). Заметим, что достаточно проверить условие для одной циклической траектории (в силу линейности свёртки). Будем мыслить такую траекторию как набор диагональных отрезков между последовательными отражениями, где каждый отрезок увеличивает значения каждого датчика, который на нём расположен, на 1◦ C. Легко видеть, что тепловая карта траектории равна сумме тепловых карт этих отрезков. Значит, достаточно проверить, что в результате свёртки с ядром K любой матрицы, в которой единицы расположены на одном диагональном ряду (остальные элементы — нули), получается нулевая матрица. Это следует из того, что сумма элементов матрицы K на любом диагональном ряду равна нулю. Остаётся доказать, что меньшие значения s не подходят. Достаточно доказать это для s = 2. Предположим обратное, что есть определяющее ядро   a b K= , c d Рассмотрим циклическую траекторию, аналогичную приведённой в условии на втором рисунке (проходящую через два противоположных угла). Легко видеть, что если расставить в центры клеток, которые она пересекает, 300 устройств, можно добиться, чтобы получилась только эта траектория (нужно направить все лучи из устройств «вдоль» неё). Пусть она проходит через левый нижний угол (0, 0). Тогда после свёртки с ядром K мы получим B[1][0] = 2a и B[0][1] = 2d. Итого a = d = 0. Рассуждая аналогично для траектории, проходящей через два других угла, мы получим, что b = c = 0, противоречие.

Страница 6 из 11

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Первый тур. Москва, 23 марта 2026 г.

E. Новая выборка Модель по вещественному числу x предсказывает вещественное число методом линейной регрессии, то есть по формуле f (x) = ax + b, где a, b ∈ R — параметры модели f . Дана обучающая выборка (x1 , y1 ), (x2 , y2 ), . . . (xn , yn ), где x1 , . . . , xn , y1 , . . . yn ∈ R. Модель f обучается на этих данных: параметры a и b выбираются методом наименьших квадратов, то есть так, чтобы значение выражения n X

(yi − f (xi ))2

i=1

было минимальным. Определим коэффициент детерминации R02 , посчитанный на этой выборке, следующим образом: n P

(yi − f (xi ))2

R02 = 1 − i=1 n P

, (yi

− ȳ)2

1X yi . где ȳ = n i=1

i=1

Построим новую выборку, добавив к исходной n объектов (xi , f (xi )), i = 1, 2, . . . , n. Пусть на получившейся выборке из 2n объектов обучается модель g(x) = cx + d также методом наименьших квадратов. Обозначим через R12 коэффициент детерминации для модели g, посчитанный на выборке из всех 2n объектов. Предполагается, что параметры обеих моделей удалось подобрать методом наименьших квадратов единственным образом. Выразите R12 через R02 .

Решение. Ответ: R12 =

2R02 . 1 + R02

Положим ybi = f (xi ), (предсказание); SSE0 = SST0 =

n P

n P i=1

(yi − ybi )2 (сумма квадратов ошибок),

(yi − ȳ)2 (общая сумма квадратов). Тогда R02 = 1 −

i=1

SSE0 . SST0

Аналогично определим величины SSE1 и SST1 для второй модели. Лемма 1. Модель, обученная на объединённой выборке, совпадает с исходной, и потому SSE1 = SSE0 . Доказательство. Для модели f на добавленных точках (xi , ybi ) ошибки (b yi − f (xi ))2 равны нулю, а для исходных точек сумма квадратов ошибок — наименьшая возможная. Значит, минимум на объединённой выборке достигается для тех же значений параметров (тем самым, модели совпадают). Лемма 2. Имеют место равенства

n X

(yi − ybi ) = 0 и

i=1

Страница 7 из 11

n X i=1

xi (yi − ybi ) = 0.

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Первый тур. Москва, 23 марта 2026 г.

Доказательство. Точка (a, b) выбирается методом наименьших квадратов, то есть совпадает с наименьшем значении функции n X F (u, v) = (yi − uxi − v)2 . i=1

Значит, если зафиксировать u = a у получившейся квадратичной функции от переменной v наименьшее значение достигается в точке b. Это означает, что v-координата вершины соответствующей параболы равна b, то есть ! n n n n X X X 1 X (axi − yi ) =⇒ 0 = (axi + b) − yi = (b yi − yi ). −b = n i=1 i=1 i=1 i=1 Рассуждая аналогично для v = b, получаем второе заявленное соотношение: −a ·

n X

x2i =

i=1

n n X X (b − yi )xi =⇒ 0 = xi (axi + b − yi ) = xi (b yi − yi ). i=1

i=1

Лемма 3. Имеет место равенство SST1 = 2SST0 − SSE0 . n

1X Доказательство. Согласно лемме 2, справедливо равенство ybi = ȳ. Значит, n i=1 SST1 =

n X

(yi − ȳ)2 +

i=1

n X

(b yi − ȳ)2 = SST0 +

i=1

n X (b yi − ȳ)2 . i=1

Кроме того, yi − ȳ = (yi − ybi ) + (b yi − ȳ). Возведём все такие равенства в квадрат, после чего просуммируем. Мы получим, что n n n X X X 2 2 SST0 = (yi − ybi ) + (b yi − ȳ) + 2 (yi − ybi )(b yi − ȳ). i=1

i=1

i=1

Последний член равен нулю. Действительно, воспользуемся леммой 2 следующим образом: n X

(yi − ybi )(b yi − ȳ) =

i=1

n X

(yi − ybi )b yi = a

i=1

Тогда SST0 = SSE0 +

n P

n X

n X xi (yi − ybi ) + b (yi − ybi ) = 0.

i=1

i=1

(b yi − ȳ)2 , что и даёт требуемое равенство.

i=1

Cобирая вместе результаты лемм 1–3, имеем: R12 = 1 −

SSE1 SSE0 2(SST0 − SSE0 ) =1− = . SST1 2SST0 − SSE0 2SST0 − SSE0

Разделив числитель и знаменатель на SST0 , мы получаем заявленный ответ: R12 =

0 2(1 − SSE ) SST0 0 2 − SSE SST0

2R02 . 1 + R02

Комментарий. Лемму 2 можно доказать по-другому, воспользовавшись тем, что частные производных функции F (u, v) в точке (a, b) равны нулю. Страница 8 из 11

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Первый тур. Москва, 23 марта 2026 г.

F. Вымышленная ситуация Артур и Таня готовят вычислительные мощности к практическому туру для 1000 участников финала всероссийской олимпиады школьников по ИИ. Изначально они хотели развернуть всю инфраструктуру в облаке и выдавать видеокарты (GPU) из общего набора, но, чтобы исключить сетевые задержки, было решено собрать каждому участнику персональный кластер из 8 устройств. На складе партнёров олимпиады есть по 8000 устройств каждого из трех типов. • Тип 1 (зеленые) — CUDA, быстрые, но горячие. • Тип 2 (красные) — поддерживают открытые стандарты, подходят для обучения больших языковых моделей (LLM), но требуют сложной настройки драйверов. • Тип 3 (желтые) — экспериментальные ускорители (TPU). По условиям поставки устройства типа 1 можно получать только партиями по 7 штук; устройств каждого типа должно быть не меньше, чем 2026; всего со склада следует взять ровно 8000 устройств. Если в соответствии с этими условиями можно привезти со склада y1 устройств первого типа, y2 — второго и y3 — третьего, будем называть тройку целых чисел (y1 , y2 , y3 ) допустимой, то есть: y1 + y2 + y3 = 8000,

yt ⩾ 2026 (t = 1, 2, 3),

. y1 .. 7.

Каждый участник имеет персональный шифр i = 1, 2, . . . , 1000. Устройства, поставленные со склада, распределяются между участниками. Пусть участник i получает персональный кластер Si = (xi,1 , xi,2 , xi,3 ), в котором xi,1 , xi,2 , xi,3 — количество устройств первого, второго и третьего типа соответственно. Числа xi,1 , xi,2 , xi,3 — целые неотрицательные, причём xi,1 + xi,2 + xi,3 = 8. Оказалось, что кто-то из участников предпочитает «зеленых», кто-то фанат открытых стандартов и выбирает «красных», а кто-то любит необычных «жёлтых». Для каждого участника заданы предпочтения устройств ui,1 , ui,2 , ui,3 , указанные во входном файле gpu.csv. Файл содержит заголовок и 1000 строк с 3 столбцами с данными: в i-й строке записаны три целых числа ui,1 , ui,2 , ui,3 (именно в таком порядке), соответствующие предпочтениям участника i. Полезность кластера S = (x1 , x2 , x3 ) для участника i задаётся формулой Vi (S) = ui,1 x1 + ui,2 x2 + ui,3 x3 . Распределение устройств называется честным, если неравенство Vi (Si ) ⩾ Vi (Sj ) справедливо для любых двух участников i, j. (a) Приведите пример допустимой тройки значений (y1 , y2 , y3 ), при которой честного распределения не существует, и докажите это. (b) Существует ли допустимая тройка (y1 , y2 , y3 ), при которой существует честное распределение?

Решение. Будем называть величины ui,j полезностями устройств типа j для участника i. Страница 9 из 11

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Первый тур. Москва, 23 марта 2026 г.

Лемма 1. Если умножить значения полезностей произвольного участника i на одно и то же положительное число, любое честное распределение останется честным, а любое нечестное — нечестным. Доказательство. Обе части неравенств, в которых фигурирует полезность Vi , мы умножим на одну и ту же положительную константу, тем самым выполнение системы новых и изначальных неравенств равносильно. Наблюдение. По данным файла gpu.csv, школьники делятся на две группы. • группа A: 511 школьников, набор полезностей которых пропорционален uA = (2053, 2063, 2070); • группа B: 489 школьников, набор полезностей которых пропорционален uB = (2081, 2027, 2003). В силу леммы 1, можно считать, что всем участникам группы A соответствует набор полезностей uA , а всем участникам группы B — набор полезностей uB . Лемма 2. В любом честном распределении все школьники группы B получают один и тот же набор устройств. Доказательство. Возьмём двух участников группы B. Пусть первый получил bi устройств типа i, второй — b′i устройств типа i. Поскольку предпочтения у этих участников совпадают, полезности кластеров (b1 , b2 , b3 ) и (b′1 , b′2 , b′3 ) для них также совпадают и равны между собой. То есть 2081b1 + 2027b2 + 2003b3 = 2087b′1 + 2027b′2 + 2003b′3 . Поскольку в обоих кластерах по 8 устройств, вычитая из обеих частей 2003 · 8, получаем (2081 − 2003)b1 + (2027 − 2003)b2 = (2081 − 2003)b′1 + (2027 − 2003)b′2 ⇒ 78(b1 − b′1 ) + 24(b1 − b′2 ) = 0 ⇒ 13(b1 − b′1 ) + 4(b2 − b′2 ) = 0 Значит, число b2 − b′2 кратно 13. Однако, поскольку 0 ⩽ b2 , b′2 ⩽ 8, это возможно лишь в случае b2 = b′2 , а тогда и b1 = b′1 , b3 = b′3 , что завершает доказательство леммы. Лемма 3. В любом честном распределении все школьники группы A получают один и тот же набор устройств. Доказательство. Рассуждение, в целом, аналогично предыдущей лемме. Вновь, предполагая противная, имеем два кластера (a1 , a2 , a3 ) и (a′1 , a′2 , a′3 ) одинаковой полезности для участника группы A. Теперь, поскольку 2053 − 2070 = −17 и 2063 − 2070 = −7, имеем: −17(a1 − a′1 ) − 7(a2 − a′2 ) = 0. Следовательно, a2 − a′2 делится на 17, поэтому a2 = a′2 , а тогда и a1 = a′1 , a3 = a′3 , лемма доказана. Итого все 511 школьников группы A получают один и тот же кластер a = (a1 , a2 , a3 ), а все 489 школьников группы B — один и тот же кластер b = (b1 , b2 , b3 ). Посчитаем количество устройств каждого типа: yt = 511at + 489bt

(t = 1, 2, 3).

Страница 10 из 11

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Первый тур. Москва, 23 марта 2026 г.

Пункт (a). Рассмотрим набор (y1 , y2 , y3 ) = (2100, 2100, 3800). Легко видеть, что он допустим: 2100 делится на 7, все yi не меньше, чем 2026 и 2100 + 2100 + 3800 = 8000. Докажем, что для этого набора честного распределения не существует. Предположим противное. В силу сказанного выше, для некоторых кластеров a = (a1 , a2 , a3 ) и b = (b1 , b2 , b3 ) имеет место равенство 2100 = y1 = 511a1 +489b1 . Но числа вида 2100−511n при n = 0, 1, 2, 3 не делятся на 489, а при n ⩾ 4 — отрицательны, противоречие. Пункт (b). Ответ: такой набор существует. Рассмотрим следующее распределение: каждому школьнику группы A соберём кластер (0,4,4), а каждому школьнику группы B — кластер (7,1,0). Вычислим количество устройств каждого типа: y1 = 511·0+489·7 = 3423,

y2 = 511·4+489·1 = 2044+489 = 2533,

y3 = 511·4+489·0 = 2044.

Отметим, что y1 делится на 7, и количества устройств каждого типа не меньше, чем 2026. Тем самым, такое распределение соответствует допустимой тройке (y1 , y2 , y3 ) = (3423, 2533, 2044). Проверим, что получилось честное распределение. Для любого школьника i из группы A: Vi (a) = 2053 · 0 + 2063 · 4 + 2070 · 4 = 8252 + 8280 = 16532, Vi (b) = 2053 · 7 + 2063 · 1 + 2070 · 0 = 14371 + 2063 = 16434. Значит, Vi (a) > Vi (b). Проделаем то же вычисление для школьника участника j из группы B: Vj (a) = 2081 · 0 + 2027 · 4 + 2003 · 4 = 8108 + 8012 = 16120, Vj (b) = 2081 · 7 + 2027 · 1 + 2003 · 0 = 14567 + 2027 = 16594. Следовательно, Vj (b) > Vj (a), Итого каждый участник группы B предпочитает свой кластер кластеру группы A и наоборот. Поскольку внутри каждой из групп участники получают одинаковые кластеры, распределение честное. Таким образом, подходит набор (y1 , y2 , y3 ) = (3423, 2533, 2044). Замечание. Можно доказать, что указанный набор — единственный, подходящий под условия пункта (b). Тем самым, любой другой допустимый набор подойдёт в качестве примера для пункта (a).

Страница 11 из 11

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Критерии проверки первого тура.

A. Прямая снова крутится A. Верный ответ без проверки — 45 баллов.

B. Генератор случайности A. Найдено P(6) = (m − 5)/m — 5 баллов B. Найдена вероятность последовательности из условия при фиксированном m — 15 баллов. C. Заявлено, что указанная в предыдущем критерии вероятность возрастает при целых m от 6 до 10 и убывает далее — 15 баллов D. Вероятность посчитана с ошибкой в константу раз (и другие не влияющие на ход решения неточности) — снимается не менее 5 баллов. M. Доказано, что число 10 является экстремумом. Используется, но не доказано, что это именно максимум — снимается не менее 5 баллов. • Все приведенные продвижения и штрафы суммируются.

C. Градиентный спуск на листочке Общая часть. C1 (5 баллов) Сформулировано чередование направлений спуска с периодом 4. C2 (5 баллов) Установлено убывание |X| и |Y |. Пункт (а). A. Верное доказательство пункта (а) — 20 баллов. MA. Неверно указана область остановки или используется без доказательства существование такой области — снимается 10 баллов. Пункт (b). B. Верное доказательство пункта (b) — 20 баллов. B1. Введена структура блоков, с указанием связи их размера и общим значением lr — 5 баллов. B2. Доказано, что вклад каждого блока в изменение параметров |X| и |Y | составляет не меньше некоторой константы, если все шаги этого блока приводили к изменению соответствующих координат. MB. Ошибки в изложении оставшейся части доказательства — снимается не менее 5 баллов. • Продвижения и штрафы по трем приведенным частям задачи суммируются.

D. Лазер Часть А (пример). Часть B (оценка). А1. Приведена правильная матрица K — 10 баллов. А2. Показано, что матрица обнуляет диагональные ряд — 5 баллов. А3. Завершение обоснования, почему матрица K является определяющим ядром — 10 баллов. B1. Получено линейное уравнение на элементы ядра — 5 баллов. М. Ошибки в построении «подходящияго луча» (он рассматривается только локально, ставится меньшее число устройств, чем требует условие и т.д.) — снимается не менее 5 баллов. • Все приведенные продвижения и штрафы суммируются.

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Критерии проверки первого тура.

E. Новая выборка Общеизвестные свойства линейной регрессии принимаются без доказательства при наличии верной формулировки. A. Верный ответ — 10 баллов. B. Доказано, что f = g — 10 баллов. • Если только сформулировано, что f = g, выставляется 5 баллов. • Если доказательство опирается на свойства линейной регрессии, должно быть явно сформулировано свойство добавляемых данных, которое позволяет сделать вывод о равенстве коэффициентов моделей. • Если это используется и даже не формулируется, баллы по критерию B не начисляются. • За вывод о равенстве числителей в выражениях коэффициента детерминации дополнительные баллы не начисляются. C1. Установлены зависимости между yi , xi , f (xi ) (лемма 2) — 10 баллов. C2. Получены выражения для a и b (формула коэффициентов линейной регрессии) с доказательством равенства средних — 10 баллов. • Должны быть установлены обе зависимости (в том виде, как это указано С1 или С2) и явно сформулировано равенство средних значений старой и новой выборки. За получения любого одного из соотношений начисляется 5 баллов за часть C. M. Лемма 3 (или аналогичное равенство) используется в решении без доказательства — снимается 15 баллов. • За алгебраические преобразования, не приводящие к зависимости R12 только от R02 баллы не начисляются.

F. Вымышленная ситуация A. Верный ответ в пункте (a) — 0 баллов. B. Лемма 1 (о домножении полезностей на константу) вместе с наблюдением о разделении участников на две группы — 10 баллов. B1. Указано разделение агентов делятся на 2 группы по предпочтениям – 5 балло. B2. Лемма 1 только сформлуирована — 5 баллов. C. Формулировка и доказательство леммы 2 и 3 (о равенстве кластеров внутри каждой из групп участников) — 15 баллов. C1. Формулировка леммы 2 и 3 — 5 баллов. D. Вывод пункта (а) из лемм 2 и 3 — 5 баллов. E. Ответ в пункте (б) — 10 баллов. F. Проверка ответа в пункте (б) — 10 баллов.

Решения — 2 тур

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

A. Что посмотреть? 1.1

Резюме

Этот ноутбук читает данные из файлов в той же папке, решает 3 подзадачи и сохраняет ответы в CSV: • A1 favorite_genre → answer_a1.csv • A2 actor_match → answer_a2.csv • A4 next_in_session → answer_a3.csv

1.2

Файлы, которые должен видеть ноутбук

Вход (в одной папке с ноутбуком): - queries_A.csv — запросы (по 5 кандидатов c1..c5) items_A.csv — метаданные фильмов (genre, duration) - events_A.csv — события пользователей (open/finish/like) - item_meta_A.json — метаданные фильмов (списки актёров actors) - sessions_A.json — сессии (внутри sessions[*].path) Выход (создаётся рядом с ноутбуком):

1.3

Файл

Относительный путь

Назначение

answer_a1.csv answer_a2.csv answer_a4.csv

./answer_a1.csv ./answer_a2.csv ./answer_a3.csv

ответы для favorite_genre ответы для actor_match ответы для next_in_session

Универсальный паттерн решения

Одинаковый каркас используется во всех трёх подзадачах: 1) melt переводит кандидатов c1..c5 из wide в long (по строке на кандидата). 2) merge(..., how='left') присоединяет признаки/скор, не теряя кандидатов. 3) Если есть списки (актёры, пары переходов), используем explode. Важно: пустые списки дают NaN строку (это удобно, чтобы оставить кандидата и получить вклад 0). 4) Выбор победителя делается сортировкой sort_values drop_duplicates('query_id') (первый в отсортированном порядке). 5) Сохранение — to_csv(index=False). Ссылки на документацию pandas (официально): • pandas.melt: https://pandas.pydata.org/docs/reference/api/pandas.melt.html • DataFrame.explode: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.explode.html • pandas.merge: https://pandas.pydata.org/docs/reference/api/pandas.merge.html

Страница 1 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

• DataFrame.sort_values: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.sort_values.html • DataFrame.drop_duplicates: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.drop_duplicates.html • DataFrame.to_csv: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_csv.html [1]: import pandas as pd import matplotlib.pyplot as plt 1.3.1

Быстрая проверка загрузки данных

Ниже читаем все файлы один раз, смотрим размеры таблиц и распределение query_type. [2]: Q = pd.read_csv('queries_A.csv') I = pd.read_csv('items_A.csv') E = pd.read_csv('events_A.csv') M = pd.read_json('item_meta_A.json')[['item_id','actors']] S = pd.read_json('sessions_A.json') print('Q', Q.shape, 'I', I.shape, 'E', E.shape, 'M', M.shape, 'S', S.shape) Q.query_type.value_counts() Q (800, 8) I (260, 3) E (5334, 3) M (260, 2) S (180, 2) [2]: query_type favorite_genre 400 actor_match 200 next_in_session 200 Name: count, dtype: int64 [3]: vc = Q.query_type.value_counts().sort_index() ax = vc.plot(kind='bar', title='Количество запросов по типам') ax.set_xlabel('query_type') ax.set_ylabel('count') plt.tight_layout() plt.show()

Страница 2 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

1.3.2

A1. favorite_genre

[4]: q = Q[Q.query_type == 'favorite_genre'][['query_id','user_id','c1','c2','c3','c4','c5']] e = E[['user_id','item_id','event_type']].copy() e['w'] = e.event_type.map({'open': 1, 'finish': 2, 'like': 3}) e = e.merge(I[['item_id','genre']], on='item_id', how='left') g = e.groupby(['user_id','genre'], as_index=False)['w'].sum() c = q.melt(['query_id','user_id'], ['c1','c2','c3','c4','c5'], value_name='item_id')[['query_id','user_id','item_id']] c = c.merge(I[['item_id','genre','duration']], on='item_id', how='left') c = c.merge(g, on=['user_id','genre'], how='left') c['w'] = c.w.fillna(0) c = c.sort_values(['query_id','w','duration','item_id'], ascending=[1,0,1,1]) a1 = c.drop_duplicates('query_id')[['query_id','item_id']] a1.to_csv('answer_a1.csv', index=False) a1.head() [4]:

400

query_id 1

item_id 1199

Страница 3 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

1201 1602 403 4

2 3 4 5

1065 1211 1086 1163

1.3.3

A2. actor_match

[5]: q = Q[Q.query_type == 'actor_match'][['query_id','user_id','c1','c2','c3','c4','c5']] e = E[E.event_type.isin(['finish','like'])][['user_id','item_id']] m = M[['item_id','actors']].copy() m['actors'] = m.actors.apply(lambda x: x if isinstance(x, list) else []) ua = e.merge(m, on='item_id', how='left') ua = ua.explode('actors').dropna(subset=['actors']) ua = ua.drop_duplicates(['user_id','actors']) ua['hit'] = 1 ua = ua[['user_id','actors','hit']] c = q.melt(['query_id','user_id'], ['c1','c2','c3','c4','c5'], value_name='item_id')[['query_id','user_id','item_id']] c = c.merge(m, on='item_id', how='left') c = c.explode('actors') c = c.drop_duplicates(['query_id','item_id','actors']) c = c.merge(ua, on=['user_id','actors'], how='left') c['hit'] = c.hit.fillna(0) s = c.groupby(['query_id','item_id'], as_index=False)['hit'].sum() s = s.sort_values(['query_id','hit','item_id'], ascending=[1,0,1]) a2 = s.drop_duplicates('query_id')[['query_id','item_id']] a2.to_csv('answer_a2.csv', index=False) a2.head() [5]:

4 9 14 19 24 1.3.4

query_id 401 402 403 404 405

item_id 1252 1255 1227 1216 1232

A3. next_in_session

[6]: q = Q[Q.query_type == 'next_in_session'][['query_id','user_id','c1','c2','c3','c4','c5']] fin = E[E.event_type == 'finish'][['user_id','item_id']].drop_duplicates() fin = fin.rename(columns={'item_id': 'from_item'})

Страница 4 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

t = S.explode('sessions').dropna(subset=['sessions']) t['path'] = t.sessions.apply(lambda x: x.get('path', [])) t = t[['user_id','path']] t['pair'] = t.path.apply(lambda p: list(zip(p[:-1], p[1:]))) t = t.explode('pair').dropna(subset=['pair']) t['from_item'] = t.pair.str[0] t['item_id'] = t.pair.str[1] t['hit'] = 1 tr = t.groupby(['user_id','from_item','item_id'], as_index=False)['hit']. ,→sum() c = q.melt(['query_id','user_id'], ['c1','c2','c3','c4','c5'], value_name='item_id')[['query_id','user_id','item_id']] c = c.merge(fin, on='user_id', how='left') c = c.merge(tr, on=['user_id','from_item','item_id'], how='left') c['hit'] = c.hit.fillna(0) s = c.groupby(['query_id','item_id'], as_index=False)['hit'].sum() s = s.sort_values(['query_id','hit','item_id'], ascending=[1,0,1]) a3 = s.drop_duplicates('query_id')[['query_id','item_id']] a3.to_csv('answer_a3.csv', index=False) a3.head() [6]:

2 5 11 17 20

query_id 601 602 603 604 605

item_id 1028 1071 1049 1101 1004

Страница 5 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

B. Сейсмоактивный остров [ ]: import numpy as np import pandas as pd data = np.load('data_B.npy') N = data.shape[0] m = np.nanmean(data, axis=1) # Попарная корреляция sim = np.zeros((N, N)) for i in range(N): for j in range(i, N): mask = ~np.isnan(m[i]) & ~np.isnan(m[j]) a, b = m[i, mask] - m[i, mask].mean(), m[j, mask] - m[j, mask].mean() d = np.sqrt((a**2).sum() * (b**2).sum()) if d > 0: sim[i, j] = sim[j, i] = (a * b).sum() / d # Каждому сэмплу — кластер с максимальной средней корреляцией # Инициализация: жадно labels = np.full(N, -1) reps = [] for i in range(N): best_sim, best_c = -1, -1 for c, r in enumerate(reps): if sim[i, r] > best_sim: best_sim = sim[i, r] best_c = c if best_sim > 0.35: labels[i] = best_c else: labels[i] = len(reps) reps.append(i) # Уточнение: переназначаем по средней корреляции с кластером for _ in range(10): clusters = [np.where(labels == c)[0] for c in range(max(labels) + 1)] changed = False for i in range(N): best_score, best_c = -2, labels[i] for c, members in enumerate(clusters): others = members[members != i] score = sim[i, others].mean() if len(others) > 0 else sim[i, members[0]] if score > best_score: best_score = score best_c = c if best_c != labels[i]: labels[i] = best_c

Страница 6 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

changed = True if not changed: break # Обновляем кластеры clusters = [np.where(labels == c)[0] for c in range(max(labels) + 1)] y = pd.read_csv('y.csv')['cluster'].values from sklearn.metrics import adjusted_rand_score print(f"Clusters: {len(set(labels))}, ARI = {adjusted_rand_score(y, labels):. ,→6f}") pd.DataFrame({'ID': range(N), 'target': labels}).to_csv('submission_B.csv', index=False)

Страница 7 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

C. Ошибка новичка [1]: import json import numpy as np import pandas as pd import matplotlib.pyplot as plt from catboost import CatBoostClassifier import warnings from IPython.display import display warnings.filterwarnings('ignore') pd.set_option('display.max_columns', None) pd.set_option('display.max_rows', None) # Фиксируем сид для воспроизводимости всех шагов SEED = 42 np.random.seed(SEED) # Загрузка доступных файлов train = pd.read_csv('train.csv') test = pd.read_csv('test.csv') solution = pd.read_csv('ytest.csv') with open('map.json', 'r') as f: map_data = json.load(f) display(train.head()) display(test.head()) longitude latitude 0 -68.624132 -32.868160 1 -51.964445 1.978971 2 -50.585866 -5.300425 3 -64.666741 -32.100087 4 -72.512342 -5.718456 0 1 2 3 4 \ 0 1 2 3 4 0

canopy_density 0.584679 0.327984 0.750885 0.322355 0.428812

target 0 0 0 0 1

habitat_quality_score 38.746018 17.456343 43.883046 29.736058 66.547123

soil_moisture_level 6.031727 4.694215 7.864357 4.448480 3.822563

biodiversity_index 0.869221 3.414865 1.930266 1.724559 9.257867

forest_age_years 97.911830 169.739698 214.602112 275.153342 179.540444

tree_density_per_hectare

annual_rainfall_mm

habitat_fragmentation_index

396.493164 350.579890 237.872776 458.725205 346.391230

1293.066100 2353.814818 1147.090078 1252.207036 604.782917

0.507221 0.247614 0.574802 0.648601 0.732562

predator_pressure_score 1.040142

vegetation_complexity 3.0 Страница 8 из 45

forest_type tropical_rainforest

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

1 2 3 4 0 1 2 3 4

7.522185 3.255146 6.404152 4.880406

9.0 4.0 9.0 2.0

climate_zone soil_type disturbance_level conservation_status montane peat low unprotected temperate loam minimal unprotected subtropical loam moderate unprotected temperate loam low buffer_zone tropical clay low unprotected

dominant_tree_species water_source_type 0 palm stream 1 pine none 2 palm seasonal 3 mahogany stream 4 bamboo seasonal 0 1 2 3 4

elevation_range 730.654928 316.780429 474.802210 187.292371 191.639490

0 1 2 3 4

seasonal_variation_score 6.874974 6.384739 5.690438 6.328448 5.780874

human_activity_index 0.075256 3.614124 2.251489 3.063365 3.888626

longitude latitude 0 -72.102869 19.626859 1 -50.441167 -8.396785 2 -58.372430 0.312577 3 -76.362920 -14.279388 4 -67.162159 6.450007 0 1 2 3 4 \ 0 1

dry_forest dry_forest tropical_rainforest dry_forest

canopy_density 0.648016 0.226638 0.747227 0.602197 1.000000

temperature_variability 4.037666 5.882945 3.366330 8.984897 3.966467 canopy_height_avg 6.710190 30.770752 5.000000 16.284970 36.796504

habitat_quality_score 45.511580 34.751298 73.162533 86.832262 63.631579

soil_moisture_level 3.260149 5.191137 7.533181 6.429846 4.502920

biodiversity_index 3.743753 4.712511 2.579567 4.097908 4.895632

forest_age_years 172.921350 357.568190 300.220431 325.612904 308.714905

tree_density_per_hectare

annual_rainfall_mm

habitat_fragmentation_index

226.157888 429.731562

1177.791799 1922.536202

0.558007 0.332591

Страница 9 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

2 3 4

144.840477 323.532762 693.209698

0 1 2 3 4

predator_pressure_score 7.795448 5.712367 2.073700 0.798348 1.378641

0 1 2 3 4

climate_zone soil_type disturbance_level conservation_status montane laterite high unprotected temperate sandy moderate protected subtropical loam high protected tropical alluvial minimal buffer_zone subtropical sandy minimal protected

0 1 2 3 4

dominant_tree_species water_source_type kapok stream palm seasonal pine none pine stream pine stream

temperature_variability 5.205539 8.634001 5.382953 8.282785 10.690082

0 1 2 3 4

elevation_range 318.967595 184.900374 536.571256 649.036995 388.522453

canopy_height_avg 23.686628 28.697284 26.366329 34.803212 45.697464

0 1 2 3 4

seasonal_variation_score 6.492307 4.313709 3.480140 3.417066 6.823274

1.4

1844.822680 1571.667498 1688.758358 vegetation_complexity 3.0 7.0 1.0 5.0 1.0

human_activity_index 3.626041 1.830241 0.074063 0.000000 4.500977

0.654825 0.383862 0.344897 forest_type secondary_forest dry_forest cloud_forest dry_forest tropical_rainforest

ID 0 1 2 3 4

Функция для визуализации

Создадим вспомогательную функцию для визуализации точек на карте с контуром береговой линии (map.json). [2]: def plot_map(points, coastline, color_by=None, figsize=(5, 5)): """ Визуализирует точки на карте с контуром береговой линии. Параметры: ---------points : pd.DataFrame

Страница 10 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

DataFrame с точками, должен содержать колонки 'longitude' и 'latitude' coastline : list Список координат береговой линии [[lon1, lat1], [lon2, lat2], ...] color_by : str, optional Название колонки для раскраски точек. Если None, все точки одного цвета figsize : tuple, optional Размер фигуры (ширина, высота) Пример использования: -------------------# Простая визуализация без раскраски plot_map(train, coastline) # С раскраской по таргету plot_map(train, coastline, color_by='target') # Изменение размера plot_map(train, coastline, color_by='target', figsize=(16, 10)) """ fig, ax = plt.subplots(figsize=figsize) # Рисуем береговую линию if coastline: coastline_array = np.array(coastline) # Рисуем все точки побережья как плотный scatter с квадратными маркерами ax.scatter(coastline_array[:, 0], coastline_array[:, 1], s=1.5, marker='s', color='#CCCCCC', alpha=0.7, label='Coastline') # Рисуем точки if color_by is None: # Без раскраски - все точки одного цвета ax.scatter(points['longitude'], points['latitude'], s=50, alpha=0.6, edgecolors='black', linewidths=0.5, label='Points') else: # С раскраской по указанной колонке if points[color_by].dtype in ['object', 'category']: # Категориальная переменная unique_vals = points[color_by].unique() colors = plt.cm.tab10(np.linspace(0, 1, len(unique_vals))) for i, val in enumerate(unique_vals): mask = points[color_by] == val ax.scatter(points[mask]['longitude'], points[mask]['latitude'],

Страница 11 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

5,

s=50, alpha=0.6, edgecolors='black', linewidths=0.

,→

label=f'{color_by}={val}', color=colors[i]) else: # Числовая переменная scatter = ax.scatter(points['longitude'], points['latitude'], c=points[color_by], cmap='RdYlGn', s=50, alpha=0.6, edgecolors='black', linewidths=0.5) plt.colorbar(scatter, ax=ax, label=color_by) ax.set_xlabel('Longitude') ax.set_ylabel('Latitude') ax.set_title(f'Карта точек (n={len(points)})') ax.legend() ax.grid(True, alpha=0.3) plt.tight_layout() plt.show() print("Функция plot_map() готова к использованию") Функция plot_map() готова к использованию

1.5

Демонстрация функции визуализации

Попробуем функцию на загруженных данных. [3]: # Получаем coastline из map.json coastline = map_data.get('coastline', []) # Пример 1: Train без раскраски plot_map(train, coastline) # Пример 2: Train с раскраской по таргету plot_map(train, coastline, color_by='target')

Страница 12 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

Страница 13 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

1.6

Шаг 1: Первый базовый подход

По условию задачи у нас есть train.csv (прямые наблюдения) и test.csv, где часть точек добавлена по спутниковым данным. Сначала строим честный baseline на всех признаках и смотрим F1 score на разметке из ytest.csv. Это нужно как контрольная точка, чтобы понять, насколько модель склонна переобучаться в исходной постановке. [4]: # Функция для оценки качества def evaluate(y_pred, solution): """Оценка F1-score: overall, public, private""" from sklearn.metrics import f1_score merged = solution.copy() merged['pred'] = np.asarray(y_pred).reshape(-1) # Считаем метрику на уже округленных предсказаниях (0/1) merged['pred_label'] = np.clip(np.rint(merged['pred']), 0, 1).astype(int) public_mask = merged['Usage'] == 'public' private_mask = merged['Usage'] == 'private'

Страница 14 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

overall_f1 = f1_score(merged['target'], merged['pred_label']) public_f1 = f1_score(merged.loc[public_mask, 'target'], merged. ,→loc[public_mask, 'pred_label']) private_f1 = f1_score(merged.loc[private_mask, 'target'], merged. ,→loc[private_mask, 'pred_label']) print(f"F1 overall: {overall_f1:.6f}") print(f"F1 public: {public_f1:.6f}") print(f"F1 private: {private_f1:.6f}") return public_f1, private_f1, overall_f1 # Выделяем признаки (все кроме служебных) feature_cols_numeric = [col for col in train.columns if col not in ['ID', 'target', 'cluster', 'is_train'] and train[col].dtype in ['int64', 'float64']] categorical_cols = train.select_dtypes(include=['object']).columns.tolist() categorical_cols = [col for col in categorical_cols if col not in ['ID']] all_features = feature_cols_numeric + categorical_cols X_train = train[all_features] y_train = train['target'] X_test = test[all_features] from sklearn.model_selection import train_test_split X_train_tr, X_train_val, y_train_tr, y_train_val = train_test_split( X_train, y_train, test_size=0.2, random_state=SEED, stratify=y_train ) model = CatBoostClassifier( iterations=500, learning_rate=0.1, depth=12, random_seed=SEED, verbose=0, ) model.fit(X_train_tr, y_train_tr, cat_features=categorical_cols) y_pred_test_baseline = np.asarray(model.predict(X_test)).reshape(-1). ,→astype(int) baseline_public, baseline_private, baseline_overall = evaluate(y_pred_test_baseline, solution) F1 overall: 0.646440 F1 public: 0.645707 F1 private: 0.647208

Страница 15 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

[5]: from sklearn.metrics import f1_score f1_score(solution.target.values, (solution.target.values * 0 + 0.5 >= 0.5). ,→astype(int)) [5]: 0.5270917803169922

1.7

Шаг 2: Анализ важности признаков

Связываем результат baseline с условием задачи. Если в топе важности оказываются координаты (longitude, latitude), модель может использовать географическую «подсказку» вместо устойчивых экологических закономерностей. Это и есть первая типичная ошибка новичка: неявная утечка/спуриация через признаки, которые слишком прямо кодируют разделение. [6]: # Feature importance importance_df = pd.DataFrame({ 'feature': all_features, 'importance': model.get_feature_importance() }).sort_values('importance', ascending=False) print("\nTop 10 признаков:") print(importance_df.head(10).to_string(index=False)) print("Это может означать, что модель переобучается на координаты.") print("\nВНИМАНИЕ: longitude и latitude в топе важности!") Top 10 признаков:

feature importance latitude 25.842854 habitat_quality_score 18.788004 biodiversity_index 12.708726 longitude 9.644228 forest_type 6.298411 disturbance_level 5.888199 climate_zone 2.864203 conservation_status 1.980591 seasonal_variation_score 1.594778 human_activity_index 1.516413 Это может означать, что модель переобучается на координаты. ВНИМАНИЕ: longitude и latitude в топе важности!

1.8

Шаг 3: Визуализация координат

Посмотрим, как распределены классы в пространстве координат. [7]: # Используем нашу функцию для визуализации print("Train данные с раскраской по таргету:") plot_map(train, coastline, color_by='target', figsize=(10, 8))

Страница 16 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

# Анализ распределения классов print("\n" + "="*70) print("АНАЛИЗ КООРДИНАТ") print("="*70) # Статистика по долготе print("\nРаспределение классов по долготе:") for target_val in [0, 1]: subset = train[train['target'] == target_val] print(f"\nКласс {target_val}:") print(f" Longitude: min={subset['longitude'].min():.2f}, max={subset['longitude'].max():.2f}, mean={subset['longitude'].mean():.2f}") print(f" Latitude: min={subset['latitude'].min():.2f}, max={subset['latitude'].max():.2f}, mean={subset['latitude'].mean():.2f}") print("\n" + "="*70) print("ВЫВОД: Классы четко разделены по координатам!") print(" Модель запомнила географическую границу между классами.") print(" В реальном тесте эта закономерность не работает.") print(" РЕШЕНИЕ: Нужно убрать longitude и latitude из признаков.") print("="*70) Train данные с раскраской по таргету:

Страница 17 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

====================================================================== АНАЛИЗ КООРДИНАТ ====================================================================== Распределение классов по долготе: Класс 0: Longitude: min=-91.65, max=-39.05, mean=-64.11 Latitude: min=-55.12, max=22.89, mean=-10.67 Класс 1: Longitude: min=-90.20, max=-38.22, mean=-62.39 Latitude: min=-29.95, max=-0.10, mean=-15.78 ====================================================================== ВЫВОД: Классы четко разделены по координатам! Модель запомнила географическую границу между классами. В реальном тесте эта закономерность не работает. РЕШЕНИЕ: Нужно убрать longitude и latitude из признаков. ======================================================================

1.9

Шаг 4: Убираем координаты и переобучаем модель

Исправляем первую ошибку: убираем longitude и latitude, чтобы модель опиралась на содержательные признаки среды. Снова считаем F1 score и сравниваем с baseline. Если качество растет, значит координаты действительно мешали обобщению. [8]: # Убираем координаты features_no_coords = [f for f in all_features if f not in ['longitude', 'latitude']] categorical_cols_filtered = [c for c in categorical_cols if c in features_no_coords] X_train_no_coords = train[features_no_coords] X_test_no_coords = test[features_no_coords] X_train_tr_no_coords, X_train_val_no_coords, y_train_tr_no_coords, y_train_val_no_coords = train_test_split( X_train_no_coords, y_train, test_size=0.2, random_state=SEED, stratify=y_train ) model.fit(X_train_tr_no_coords, y_train_tr_no_coords, cat_features=categorical_cols_filtered) y_pred_test_no_coords = np.asarray(model.predict(X_test_no_coords)). ,→reshape(-1).astype(int)

Страница 18 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

nocoords_public, nocoords_private, nocoords_overall = evaluate(y_pred_test_no_coords, solution) F1 overall: 0.783972 F1 public: 0.798155 F1 private: 0.769591

1.10

Шаг 5: Детектор train vs test (поиск сдвига распределения)

Проверяем вторую ошибку новичка из условия: игнорирование того, что train и часть test собраны разными способами. Если модель легко отличает train от test, это признак domain shift (распределения различаются). Тогда у части test-точек стандартная модель может быть слишком уверенной и ошибаться системно. [9]: from catboost import cv, Pool # Создаем датасет для классификации train vs test X_combined = pd.concat([X_train_no_coords, X_test_no_coords], axis=0) y_combined = np.concatenate([np.ones(len(X_train_no_coords)), np. ,→zeros(len(X_test_no_coords))]) model_ood = CatBoostClassifier( iterations=50, learning_rate=0.1, depth=6, random_seed=SEED, verbose=0 ) cv_data = Pool(data=X_combined, label=y_combined, cat_features=categorical_cols_filtered) _ = cv( pool=cv_data, params={ 'iterations': 50, 'learning_rate': 0.1, 'depth': 6, 'loss_function': 'Logloss', 'eval_metric': 'AUC', 'random_seed': SEED, 'verbose': False }, fold_count=5, shuffle=True, stratified=True, partition_random_seed=SEED ) model_ood.fit(X_combined, y_combined, cat_features=categorical_cols_filtered) test_similarity_scores = model_ood.predict(X_test_no_coords, prediction_type='Probability')[:, 1] Страница 19 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

plt.figure(figsize=(10, 5)) plt.hist(test_similarity_scores, bins=100, alpha=0.7, edgecolor='black') plt.xlabel('Вероятность принадлежности к train') plt.ylabel('Количество объектов') plt.title('Train/Test Classifier: Распределение вероятностей на test') plt.grid(True, alpha=0.3) plt.tight_layout() plt.show() Training on fold [0/5] bestTest = 0.6328465347 bestIteration = 9 Training on fold [1/5] bestTest = 0.6250990099 bestIteration = 4 Training on fold [2/5] bestTest = 0.6888 bestIteration = 6 Training on fold [3/5] bestTest = 0.6909022556 bestIteration = 11 Training on fold [4/5] bestTest = 0.6967669173 bestIteration = 16

Страница 20 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

1.11

Шаг 6: OOD-корректировка предсказаний

Используем train/test-детектор как индикатор OOD-точек (похожести тестовой точки на train). Для точек с очень низкой похожестью понижаем вероятность класса 1 и снова считаем только F1 score. Так мы адресуем вторую ошибку: учитываем сдвиг распределения и делаем предсказания устойчивее на «спутниковой» части теста. [10]: print("\nКорректируем предсказания для OOD объектов...") print("="*70) # Порог OOD: точки с низкой похожестью на train считаем сдвинутыми по распределению ood_threshold = 0.07 y_pred_corrected = y_pred_test_no_coords.copy() ood_mask = test_similarity_scores < ood_threshold y_pred_corrected[ood_mask] = 0 # Выводим F1-score для каждого подхода: overall/public/private _, _, _ = evaluate(y_pred_test_baseline, solution) _, _, _ = evaluate(y_pred_test_no_coords, solution) _, _, corrected_overall = evaluate(y_pred_corrected, solution) Корректируем предсказания для OOD объектов... ====================================================================== F1 overall: 0.646440 F1 public: 0.645707 F1 private: 0.647208 F1 overall: 0.783972 F1 public: 0.798155 F1 private: 0.769591 F1 overall: 0.918549 F1 public: 0.918340 F1 private: 0.918768 [11]: # Сохраняем submission с лучшими доступными предсказаниями final_pred = y_pred_corrected submission = pd.DataFrame({ 'ID': test['ID'].values, 'target': (final_pred > 0.5) * 1 }) submission.to_csv('author_submission.csv', index=False)

Страница 21 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

D. Наследие Человечества [1]: # Шаг 0. Импорты import csv import json from pathlib import Path import numpy as np from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.metrics.pairwise import cosine_similarity from sklearn.preprocessing import StandardScaler # Фиксируем генератор случайных чисел для воспроизводимости rng = np.random.default_rng(42)

1.12

Разбор решения по шагам

Решения идут по порядку и усложняются шаг за шагом: 1. Решение 0: IOU/Jaccard по словам. 2. Решение 1: unigram TF-IDF. 3. Решение 2: word+char TF-IDF. 4. Решение 3: решение 2 + greedy one-to-one. 5. Решение 4: решение 3 + logreg-reranker. 6. Решение 5: решение 4 + greedy one-to-one. 1.12.1

Шаг 1. Загрузка train/test/ytest

Читаем train.json и test.json, восстанавливаем train-пары, а true_test и разбиение Public/Private строим по ytest.csv через соответствия left_id ↔ right_id и колонку Usage. [2]: # Шаг 1. Загрузка train/test/ytest (ещё компактнее) base_dir = Path("/Users/aguschin/Git/uni/vsosh/zakl/nlp") train_json_path, test_json_path, ytest_csv_path = ( base_dir / "train.json", base_dir / "test.json", base_dir / "ytest.csv", ) def split_poem(text): lines = [line.strip() for line in text.split("\n") if line.strip()] cut = min(8, len(lines) - 1) if cut < 1: return None, None left, right = "\n".join(lines[:cut]).strip(), "\n".join(lines[cut:]). ,→strip() return (left, right) if left and right else (None, None) # train: из полных текстов -> пары половинок -> shuffle правых train_pairs = [split_poem(text) for text in json.loads(train_json_path. ,→read_text(encoding="utf-8"))] train_pairs = [(left, right) for left, right in train_pairs if left and right] Страница 22 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

left_parts = [left for left, _ in train_pairs] right_parts = [right for _, right in train_pairs] train_idx = np.arange(len(left_parts), dtype=int) perm_train = np.random.default_rng(123).permutation(len(left_parts)) left_train = left_parts right_train_shuffled = [right_parts[i] for i in perm_train] true_train = np.empty(len(left_parts), dtype=int) true_train[perm_train] = np.arange(len(left_parts)) n_train = len(left_train) # test: готовые left/right test_data = json.loads(test_json_path.read_text(encoding="utf-8")) left_items, right_items = test_data["left"], test_data["right"] left_test = [item["left_text"] for item in left_items] left_ids = [item["left_id"] for item in left_items] right_test_shuffled = [item["right_text"] for item in right_items] right_ids = [item["right_id"] for item in right_items] n_test = len(left_test) # ytest: left_id -> right_id и Usage with open(ytest_csv_path, "r", encoding="utf-8") as f: ytest_rows = [row for row in csv.DictReader(f)] left_to_right = {} left_to_usage = {} for row in ytest_rows: left_id = (row.get("left_id") or "").strip() right_id = (row.get("right_id") or "").strip() usage = (row.get("Usage") or "Private").strip().title() if not left_id or not right_id: continue if usage not in {"Public", "Private"}: usage = "Private" left_to_right[left_id] = right_id left_to_usage[left_id] = usage right_id_to_pos = {rid: j for j, rid in enumerate(right_ids)} true_test = np.array([right_id_to_pos[left_to_right[lid]] for lid in left_ids], dtype=int) usage_test = np.array([left_to_usage.get(lid, "Private") for lid in left_ids], dtype=object) public_mask = usage_test == "Public" private_mask = usage_test == "Private" # проверки и служебные переменные assert all(lid in left_to_right for lid in left_ids), "Не все left_id из test есть в ytest.csv" assert all(left_to_right[lid] in right_id_to_pos for lid in left_ids), "Некоторые right_id из ytest.csv отсутствуют в test.json" vowels = set("аеёиоуыэюя")

Страница 23 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

test_idx = np.arange(n_test, dtype=int) + 10_000_000 overlap = len(set(train_idx.tolist()) & set(test_idx.tolist())) assert overlap == 0, "train/test overlap detected" print(f"Источник train: {train_json_path}") print(f"Источник test : {test_json_path}") print(f"Источник gt : {ytest_csv_path}") print(f"Всего пар: Train: {n_train} | Test: {n_test}") print(f"Usage split: Public={int(public_mask.sum())} | Private={int(private_mask.sum())}") print(f"Честность split: overlap(train,test)={overlap}\n") Источник train: /Users/aguschin/Git/uni/vsosh/zakl/nlp/train.json Источник test : /Users/aguschin/Git/uni/vsosh/zakl/nlp/test.json Источник gt : /Users/aguschin/Git/uni/vsosh/zakl/nlp/ytest.csv Всего пар: Train: 2000 | Test: 640 Usage split: Public=320 | Private=320 Честность split: overlap(train,test)=0

1.12.2

Решение 0 — IOU/Jaccard по словам

Базовый скор: для каждой пары половинок считаем пересечение слов и делим на объединение. Оценка/выбор пары: top1 через argmax (greedy matching не используется). [3]: # Шаг 2. Решение 0: IOU/Jaccard через split() # Решение 0: базовый скор по пересечению слов def _masked_acc(pred_idx: np.ndarray, true_idx: np.ndarray, mask: np. ,→ndarray): return float((pred_idx[mask] == true_idx[mask]).mean()) if mask.any() else float("nan") # Функция: считает единый score через argmax (Overall/Public/Private) def evaluate_similarity_argmax(sim: np.ndarray, true_idx: np.ndarray): pred = sim.argmax(axis=1) score = float((pred == true_idx).mean()) score_public = _masked_acc(pred, true_idx, public_mask) score_private = _masked_acc(pred, true_idx, private_mask) return { "score": score, "score_public": score_public, "score_private": score_private, } # Функция: превращает каждый текст в множество токенов def _token_sets(texts): return [set(text.split()) for text in texts] # Функция: строит матрицу Jaccard/IOU для всех пар left-right

Страница 24 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

def _jaccard_matrix(left_sets, right_sets): n_left = len(left_sets) n_right = len(right_sets) sim = np.zeros((n_left, n_right), dtype=np.float32) for i, lset in enumerate(left_sets): for j, rset in enumerate(right_sets): union = lset | rset if union: sim[i, j] = len(lset & rset) / len(union) return sim word_train_l = _token_sets(left_train) word_train_r = _token_sets(right_train_shuffled) word_test_l = _token_sets(left_test) word_test_r = _token_sets(right_test_shuffled) sim0_test = _jaccard_matrix(word_test_l, word_test_r) metrics0 = evaluate_similarity_argmax(sim0_test, true_test) print( f"0_iou_argmax: " f"score={metrics0['score']:.4f} (pub={metrics0['score_public']:.4f}, priv={metrics0['score_private']:.4f})" ) 0_iou_argmax: score=0.1734 (pub=0.1594, priv=0.1875) 1.12.3

Решение 1 — unigram TF-IDF

Улучшаем решение 0: заменяем простое пересечение слов на TF-IDF-представление слов и косинусную близость. [4]: # Шаг 2. Решение 1: unigram TF-IDF # Решение 1: улучшаем решение 0 с помощью TF-IDF признаков vec1 = TfidfVectorizer(ngram_range=(1, 1), min_df=5, max_df=0.95, sublinear_tf=False) vec1.fit(left_train + right_train_shuffled) sim1_test = cosine_similarity(vec1.transform(left_test), vec1. ,→transform(right_test_shuffled)) metrics1 = evaluate_similarity_argmax(sim1_test, true_test) print( f"1_unigram_tfidf: " f"score={metrics1['score']:.4f} (pub={metrics1['score_public']:.4f}, priv={metrics1['score_private']:.4f})" ) 1_unigram_tfidf: score=0.2734 (pub=0.2562, priv=0.2906) 1.12.4

Решение 2 — word+char TF-IDF

Улучшаем решение 1: смешиваем similarity из word TF-IDF и char TF-IDF, затем считаем argmax-метрики. Страница 25 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

[5]: # Шаг 3. Решение 2: word+char TF-IDF # Функция: строит blended similarity из word и char TF-IDF def _build_tfidf_blend(left_train, right_train, left_eval, right_eval, cfg, alpha): vec_word = TfidfVectorizer(analyzer="word", **cfg["word"] ) vec_char = TfidfVectorizer(analyzer="char_wb", **cfg["char"] ) vec_word.fit(left_train + right_train) vec_char.fit(left_train + right_train) sim_word = cosine_similarity(vec_word.transform(left_eval), vec_word. ,→transform(right_eval)) sim_char = cosine_similarity(vec_char.transform(left_eval), vec_char. ,→transform(right_eval)) return alpha * sim_word + (1.0 - alpha) * sim_char cfg2b = { "word": dict(ngram_range=(1, 2), min_df=2, max_df=0.95, sublinear_tf=True), "char": dict(ngram_range=(2, 5), min_df=1, max_df=0.99, sublinear_tf=True), } alpha2b = 0.25 # Решение 2: улучшаем решение 1 смешением word/char признаков sim2b_train = _build_tfidf_blend(left_train, right_train_shuffled, left_train, right_train_shuffled, cfg2b, alpha2b) sim2b_test = _build_tfidf_blend(left_train, right_train_shuffled, left_test, right_test_shuffled, cfg2b, alpha2b) metrics2 = evaluate_similarity_argmax(sim2b_test, true_test) print( f"2_word_char_tfidf: " f"score={metrics2['score']:.4f} (pub={metrics2['score_public']:.4f}, priv={metrics2['score_private']:.4f})" ) 2_word_char_tfidf: score=0.4938 (pub=0.4969, priv=0.4906) 1.12.5

Решение 3 — TF-IDF + greedy one-to-one

Берём матрицу скоров решения 2 и добавляем жадное взаимно-однозначное сопоставление для роста one2one. [6]: # Функция: строит жадное взаимно-однозначное соответствие def greedy_one2one_matching(sim: np.ndarray): n = sim.shape[0] flat_order = np.argsort(sim, axis=None)[::-1] pred = np.full(n, -1, dtype=int) used_left = np.zeros(n, dtype=bool) used_right = np.zeros(n, dtype=bool) matched = 0 for idx in flat_order: Страница 26 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

i, j = divmod(idx, n) if (not used_left[i]) and (not used_right[j]): pred[i] = j used_left[i] = True used_right[j] = True matched += 1 if matched == n: break return pred # Функция: считает единый score через greedy def evaluate_similarity_greedy(sim: np.ndarray, true_idx: np.ndarray): pred = greedy_one2one_matching(sim) score = float((pred == true_idx).mean()) score_public = _masked_acc(pred, true_idx, public_mask) score_private = _masked_acc(pred, true_idx, private_mask) return { "score": score, "score_public": score_public, "score_private": score_private, } # Решение 3: добавляем greedy к решению 2 metrics3 = evaluate_similarity_greedy(sim2b_test, true_test) print( f"3_tfidf_greedy: " f"score={metrics3['score']:.4f} (pub={metrics3['score_public']:.4f}, priv={metrics3['score_private']:.4f})" ) 3_tfidf_greedy: score=0.5062 (pub=0.5094, priv=0.5031) 1.12.6

Решение 4 — logreg reranker

Улучшаем решение 3: добавляем pairwise-фичи и логистическую регрессию, которая переоценивает кандидатов перед argmax. [7]: # Коротко: считаем статистики половинок и собираем pairwise-feature cube def _half_stats(texts): stats = [] for text in texts: lines = [line.strip() for line in text.split("\n") if line.strip()] or [text.strip()] words = ["".join(ch for ch in w if ch.isalpha()) for w in text. ,→replace("\n", " ").split()] words = [w for w in words if w] letters = [ch for ch in text if ch.isalpha()] v_all = [sum(1 for ch in line.lower() if ch in vowels) for line in lines] v_edge = [sum(1 for ch in line.lower() if ch in vowels) for line in lines[-4:]]

Страница 27 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

cap_ratio = (sum(1 for w in words if w[0].isupper()) / len(words)) if words else 0.0 latin_ratio = (sum(1 for ch in letters if 'a' <= ch.lower() <= 'z') / len(letters)) if letters else 0.0 stats.append([len(lines), np.mean([len(line) for line in lines]), np. ,→mean(v_all), np.mean(v_edge), cap_ratio, latin_ratio]) return np.asarray(stats, dtype=np.float32) # Коротко: переводим разницу скалярного признака в similarity def _pair_sim(left_vals, right_vals): diff = np.abs(left_vals[:, None] - right_vals[None, :]) norm = np.maximum(left_vals[:, None], right_vals[None, :]) return 1.0 - diff / (norm + 1e-12) # Коротко: формируем обучающие пары (true + hard negatives + random negatives) def _sample_pairs(cube, true_idx, hard_source_sim, hard_k=8, rand_k=8, random_state=42): rng_local = np.random.default_rng(random_state) n = cube.shape[0] rows, labels = [], [] for i in range(n): j_true = true_idx[i] rows.append(cube[i, j_true]); labels.append(1) hard_neg = [j for j in np.argsort(-hard_source_sim[i]) if j != j_true][:hard_k] forbidden = set(hard_neg + [j_true]) pool = np.array([j for j in range(n) if j not in forbidden], dtype=int) rand_neg = rng_local.choice(pool, size=min(rand_k, len(pool)), replace=False).tolist() if len(pool) else [] for j in hard_neg + rand_neg: rows.append(cube[i, j]); labels.append(0) return np.asarray(rows, dtype=np.float32), np.asarray(labels, dtype=np. ,→int32) left_train_feat = _half_stats(left_train) right_train_feat = _half_stats(right_train_shuffled) left_test_feat = _half_stats(left_test) right_test_feat = _half_stats(right_test_shuffled) train_maps = [sim2b_train] + [_pair_sim(left_train_feat[:, k], right_train_feat[:, k]) for k in range(left_train_feat.shape[1])] + [_jaccard_matrix(word_train_l, word_train_r).astype(np.float32)] test_maps = [sim2b_test] + [_pair_sim(left_test_feat[:, k], right_test_feat[: ,→, k]) for k in range(left_test_feat.shape[1])] + [_jaccard_matrix(word_test_l, word_test_r).astype(np.float32)] cube_train = np.stack(train_maps, axis=2).astype(np.float32) cube_test = np.stack(test_maps, axis=2).astype(np.float32)

Страница 28 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

x_train, y_train = _sample_pairs(cube_train, true_train, sim2b_train, hard_k=8, rand_k=8, random_state=42) scaler = StandardScaler() model = LogisticRegression(C=0.03, max_iter=2000, class_weight="balanced", solver="lbfgs", random_state=42) model.fit(scaler.fit_transform(x_train), y_train) flat_scores = model.predict_proba(scaler.transform(cube_test.reshape(-1, cube_test.shape[-1])))[:, 1] test_scores_4 = flat_scores.reshape(cube_test.shape[0], cube_test.shape[1]) alpha4 = 0.82 sim4_test = alpha4 * test_scores_4 + (1.0 - alpha4) * sim2b_test metrics4 = evaluate_similarity_argmax(sim4_test, true_test) print( f"4_logreg_rerank: " f"score={metrics4['score']:.4f} (pub={metrics4['score_public']:.4f}, priv={metrics4['score_private']:.4f})" ) 4_logreg_rerank: score=0.5203 (pub=0.5031, priv=0.5375) 1.12.7

Решение 5 — logreg reranker + greedy

Финальное улучшение: к score-матрице решения 4 снова применяем greedy one-to-one для максимального one2one. [8]: # Решение 5: добавляем greedy к скалам решения 4 metrics5 = evaluate_similarity_greedy(sim4_test, true_test) print( f"5_logreg_rerank_greedy: " f"score={metrics5['score']:.4f} (pub={metrics5['score_public']:.4f}, priv={metrics5['score_private']:.4f})" ) 5_logreg_rerank_greedy: score=0.5656 (pub=0.5687, priv=0.5625) [10]: # Сохраняем сабмит для автора из лучшего решения sim_test = sim4_test pred_idx = sim_test.argmax(axis=1) author_submission_path = base_dir / "author_submission.csv" with open(author_submission_path, "w", newline="", encoding="utf-8") as f: writer = csv.writer(f) writer.writerow(["left_id", "right_id"]) for i, lid in enumerate(left_ids): writer.writerow([lid, right_ids[int(pred_idx[i])]]) print(f"Saved {author_submission_path}") Saved /Users/aguschin/Git/uni/vsosh/zakl/nlp/author_submission.csv

Страница 29 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

1.12.8

Шаг 5. Результаты матчинга

Показываем только примеры матчинга для лучшего текущего решения: лучшие и худшие пары. [12]: # Шаг 5. Результаты матчинга по квантилям для лучшего решения def _preview_text(text, max_chars=220, max_lines=8): lines = [line.strip() for line in text.split("\n") if line.strip()][: ,→max_lines] preview = "\n".join(lines) return preview[:max_chars] + ("..." if len(preview) > max_chars else "") sim_test = sim4_test pred_idx = sim_test.argmax(axis=1) quantiles_to_show = [0.99, 0.75, 0.5, 0.25, 0.0] # можно менять вручную if not quantiles_to_show or any(q < 0 or q > 1 for q in quantiles_to_show): raise ValueError("Квантили должны быть в [0, 1], список не должен быть пустым") match_scores = sim_test[np.arange(len(left_test)), pred_idx] used = set() def pick_idx_for_quantile(q): target = float(np.quantile(match_scores, q)) order = np.argsort(np.abs(match_scores - target)) idx = next((int(i) for i in order if int(i) not in used), int(order[0])) used.add(idx) return idx, target print(f"Матчинг для лучшего решения") print(f"Квантили: {quantiles_to_show}") print() for q in quantiles_to_show: i, target = pick_idx_for_quantile(q) j = int(pred_idx[i]) print(f"=== q={q:.2f} | target={target:.4f} | sim={match_scores[i]:.4f} ===") print(f"left={i} -> pred_right={j} | true={true_test[i]} | correct={j == true_test[i]}") print("LEFT :", _preview_text(left_test[i])) print("RIGHT:", _preview_text(right_test_shuffled[j])) print() Матчинг для лучшего решения Квантили: [0.99, 0.75, 0.5, 0.25, 0.0] === q=0.99 | target=0.9055 | sim=0.9076 === left=383 -> pred_right=628 | true=628 | correct=True LEFT : Человек живет на белом свете. Страница 30 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

Где - не знаю. Суть совсем не в том. Я - лежу в пристрелянном кювете, Он - с мороза входит в теплый дом. Человек живет на белом свете, Он - в квартиру поднялся уже. Я - лежу в пристрелянном ... RIGHT: Человек живет на белом свете Он - в квартире зажигает свет Я - лежу в пристрелянном кювете, Я - вмерзаю в ледяной кювет. Снег не тает. Губы, щеки, веки Он засыпал. И велит дрожать... С думой о далеком человеке Легче до а... === q=0.75 | target=0.7871 | sim=0.7870 === left=208 -> pred_right=508 | true=508 | correct=True LEFT : Бронепоезда взвывают вдруг, Стылый ветер грудью разрывая. Бронепоезда идут на юг Вдоль твоих перронов, Лозовая! Звезды первую звезду зовут. Дым заката холоден и розов. Над бронеплощадками плывут RIGHT: Бескозырки черные матросов. Говорит, гремит, вздыхает бронь Отдаленно и громоподобно. И горит на станции огонь, Керосиновый огонь бездомный. Лист осенний, запоздавший лист, Братьев в путь-дорогу созывает. === q=0.50 | target=0.7115 | sim=0.7113 === left=342 -> pred_right=350 | true=350 | correct=True LEFT : Славянка тихая, сколь ток приятен твой, Когда, в осенний день, в твои глядятся воды Холмы, одетые последнею красой Полуотцветшия природы. Спешу к твоим брегам... свод неба тих и чист; При свете солнечном прохлада повевае... RIGHT: Иду под рощею излучистой тропой; Что шаг, то новая в глазах моих картина; То вдруг сквозь чащу древ мелькает предо мной, Как в дыме, светлая долина; То вдруг исчезло все... окрест сгустился лес; Все дико вкруг меня, и су... === q=0.25 | target=0.6497 | sim=0.6498 === left=532 -> pred_right=415 | true=415 | correct=True Страница 31 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

LEFT : Посвящается Феллини Мертвец играл на дудочке, По городу гулял, И незнакомой дурочке Он руку предлагал. А дурочка, как Золушка, Ему в глаза глядит,— Он говорит о золоте, RIGHT: О славе говорит. Мертвец, певец и умница, Его слова просты — Пусты ночные улицы, И площади пусты. «Мне больно, мне невесело, Мне холодно зимой, Возьми меня невестою, === q=0.00 | target=0.1137 | sim=0.1137 === left=533 -> pred_right=373 | true=329 | correct=False LEFT : Нет. Это неправда. Нет! И ты? Любимая, за что, за что же?! Хорошо RIGHT: Что нашу грусть — В листы, И груз — в цветы Всего за только всхруст Руки В руке: Игру. Индус, а может Златоуст

Страница 32 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

E. Подозрительные пирожные Решение состояит из следующих этапов: 1. Загружаем предобученную CNN и получаем эмбеддинги для картинок. 2. Вычисляем для каждой картинки outlier-метрики robust_mahalanobis, global_knn, class_knn. 3. Аггрегируем метрики с весами (2.0, 1.5, 0.5). 4. Сортируем картинки по значению матрики, выбираем top-K и сохраняем в submission_author.csv. [1]: import json import numpy as np import pandas as pd import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset ARTIFACTS_DIR = "." TEST_PACKAGE = f"{ARTIFACTS_DIR}/public_test_package.npz" TEST_META = f"{ARTIFACTS_DIR}/public_test_meta.json" WEIGHTS_PATH = f"{ARTIFACTS_DIR}/model_weights.pt" SUBMISSION_PATH = "submission.csv" DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") print("Device:", DEVICE) Device: cpu Код сети и получения эмбеддингов картинок: [2]: class SmallCNN(nn.Module): def __init__(self, n_classes=10): super().__init__() self.features = nn.Sequential( nn.Conv2d(1, 16, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.MaxPool2d(2), nn.Conv2d(16, 32, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.MaxPool2d(2), nn.Conv2d(32, 64, kernel_size=3, padding=1), nn.ReLU(inplace=True), ) self.fc1 = nn.Linear(64 * 8 * 8, 64) self.act = nn.ReLU(inplace=True) self.fc2 = nn.Linear(64, n_classes) def forward(self, x): z = self.features(x) z = z.flatten(1) h = self.act(self.fc1(z)) logits = self.fc2(h) return logits, h

Страница 33 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

def infer_embeddings_and_preds(model, x_np, batch_size=256): ds = TensorDataset(torch.from_numpy(x_np).float()) loader = DataLoader(ds, batch_size=batch_size, shuffle=False) all_emb, all_pred = [], [] model.eval() with torch.no_grad(): for (xb,) in loader: xb = xb.to(DEVICE) logits, emb = model(xb) all_emb.append(emb.cpu().numpy()) all_pred.append(logits.argmax(dim=1).cpu().numpy()) emb = np.concatenate(all_emb, axis=0).astype(np.float32) pred = np.concatenate(all_pred, axis=0).astype(np.int64) return emb, pred state = torch.load(WEIGHTS_PATH, map_location=DEVICE) n_classes = int(state["fc2.weight"].shape[0]) if "fc2.weight" in state else 10 model = SmallCNN(n_classes=n_classes) model.load_state_dict(state) model = model.to(DEVICE).eval() print("Model loaded") Model loaded [3]: test = np.load(TEST_PACKAGE) x_test = test["images"].astype(np.float32) K = 1000 emb_test, pred_test = infer_embeddings_and_preds(model, x_test) n_test = len(x_test) print(f"n_test={n_test}, K={K}, emb_dim={emb_test.shape[1]}") n_test=10000, K=1000, emb_dim=64 1.12.9

Идея решения

Будем искать выбросы в пространстве эмбеддингов нейросети. Предполагаем, что после прохождения через обученную модель объекты одного и того же класса образуют в пространстве признаков компактные кластеры. Тогда выбросы можно искать как точки, которые расположены “нетипично” относительно остальных точек. Для этого используются несколько типов метрик: 1. Global kNN score. Для каждого объекта считается среднее расстояние до его ближайших соседей среди всех объектов датасета. Если объект находится изолированно, его score будет большим. 2. Class kNN score. Аналогично предыдущему пункту, но соседи ищутся только среди объектов того же Страница 34 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

предсказанного класса. Это позволяет находить точки, которые выглядят необычно именно внутри своего класса, даже если глобально они не слишком далеки от других. 3. Robust Mahalanobis score внутри класса. Для каждого предсказанного класса оценивается среднее µ и матрица ковариаций Σ эмбеддингов, после чего для каждого объекта считается расстояние Махаланобиса до центра своего класса. Расстояние Махаланобиса считается так: (xi − µ)T Σ−1 (xi − µ) Идея использования ковариации в том, что Махаланобис смотрит на точку относительно формы распределения класса. Иногда точку-выброс можно поймать тем, что она отклоняется не по длине, а по “неправильному направлению” в пространстве. Чтобы выбросы не искажали изначальные оценки µ и Σ по выборке, используем робастную схему: на каждой итерации временно отбрасываем самые далёкие точки, и оцениваем параметры только по оставшемуся “ядру” класса. Таким образом, решение опирается на следующую гипотезу: выбросы — это объекты, которые в пространстве признаков либо далеки от типичного распределения своего класса, либо плохо вписываются в локальную структуру соседей. Примечание: без использования расстояния Махаланобиса можно получить до 80% баллов за задачу. Ниже реализованы функции метрик: [4]: import numpy as np def score_robust_mahalanobis(emb, pred, lam=0.15, trim_frac=0.10, iters=2): """ Считает робастный Mahalanobis score внутри каждого предсказанного класса. Идея: - для каждого класса оцениваем "нормальное" распределение эмбеддингов; - затем для каждого объекта считаем расстояние Махаланобиса до центра класса; - чтобы выбросы не портили оценку среднего и ковариации, несколько раз отбрасываем самые далёкие точки и пересчитываем статистики. Параметры: - emb: матрица эмбеддингов shape [N, d] - pred: предсказанные классы shape [N] - lam: коэффициент регуляризации ковариации - trim_frac: доля самых далёких точек, временно исключаемых при trimming - iters: число итераций робастного пересчёта """ d = emb.shape[1] # размерность пространства признаков out = np.zeros(len(emb), dtype=np.float64) # итоговый score для всех объектов eye = np.eye(d, dtype=np.float64) # единичная матрица для регуляризации Страница 35 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

# Обрабатываем каждый класс отдельно for c in np.unique(pred): m = pred == c # маска объектов класса c h = emb[m].astype(np.float64) # эмбеддинги только этого класса n = len(h) # Если объектов слишком мало, робастную оценку делать ненадёжно. # Тогда просто считаем обычное расстояние Махаланобиса. if n < 8: mu = h.mean(axis=0, keepdims=True) # центр класса cen = h - mu # центрированные эмбеддинги # Ковариация + диагональная регуляризация для устойчивости cov = (cen.T @ cen) / max(1, n - 1) + lam * eye # Псевдообратная матрица вместо обычной обратной — # устойчивее, если ковариация плохо обусловлена inv = np.linalg.pinv(cov) # Квадрат расстояния Махаланобиса для каждой точки: # (x - mu)^T inv (x - mu) out[m] = np.einsum("bi,ij,bj->b", cen, inv, cen) continue # Изначально считаем, что сохраняем все точки класса keep = np.ones(n, dtype=bool) # Начальная грубая оценка среднего и обратной ковариации mu = h.mean(axis=0, keepdims=True) inv = np.linalg.pinv(np.cov(h.T) + lam * eye) # Несколько итераций робастного trimming for _ in range(max(1, iters)): # Берём только текущие "надёжные" точки hh = h[keep] # Пересчитываем центр по оставшимся точкам mu = hh.mean(axis=0, keepdims=True) # Центрируем оставшиеся точки cen_hh = hh - mu # Пересчитываем ковариацию по очищенному подмножеству cov = (cen_hh.T @ cen_hh) / max(1, len(hh) - 1) + lam * eye inv = np.linalg.pinv(cov) # Считаем расстояния Махаланобиса уже для всех точек класса cen_all = h - mu dist_all = np.einsum("bi,ij,bj->b", cen_all, inv, cen_all)

Страница 36 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

# Порог: оставляем (1 - trim_frac) долю ближайших точек thr = float(np.quantile(dist_all, 1.0 - trim_frac)) keep = dist_all <= thr # Защита от ситуации, когда осталось слишком мало точек: # тогда ослабляем trimming if keep.sum() < max(5, int(0.5 * n)): keep = dist_all <= float(np.quantile(dist_all, 0.7)) # После финальной оценки считаем итоговый Mahalanobis score # для всех точек этого класса cen = h - mu out[m] = np.einsum("bi,ij,bj->b", cen, inv, cen) return out def score_global_knn(emb, k=10): """ Считает глобальный kNN score: среднее расстояние до k ближайших соседей по всему датасету. Если точка находится изолированно от остальных, её score будет большим. """ n = emb.shape[0] # Если точек слишком мало, meaningful score нет if n <= 2: return np.zeros(n, dtype=np.float64) # Нельзя брать соседей больше, чем число остальных точек kk = min(k, n - 1) x = emb.astype(np.float64) # Квадраты норм для всех точек x2 = np.sum(x * x, axis=1, keepdims=True) # Матрица квадратов попарных евклидовых расстояний: # ||xi - xj||^2 = ||xi||^2 + ||xj||^2 - 2 <xi, xj> d2 = x2 + x2.T - 2.0 * (x @ x.T) # Убираем расстояние точки до самой себя np.fill_diagonal(d2, np.inf) # Берём kk наименьших расстояний в каждой строке knn = np.partition(d2, kk - 1, axis=1)[:, :kk]

Страница 37 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

# Возвращаем среднее обычное расстояние до ближайших соседей return np.mean(np.sqrt(np.maximum(knn, 0.0)), axis=1) def score_class_knn(emb, pred, k=8): """ Считает class-wise kNN score: среднее расстояние до k ближайших соседей внутри предсказанного класса. Это помогает находить объекты, которые нетипичны именно для своего класса. """ out = np.zeros(len(emb), dtype=np.float64) # Считаем score отдельно внутри каждого класса for c in np.unique(pred): m = pred == c h = emb[m].astype(np.float64) n = len(h) # Если в классе слишком мало точек, score считаем нулевым if n <= 2: out[m] = 0.0 continue kk = min(k, n - 1) # Квадраты норм точек внутри класса h2 = np.sum(h * h, axis=1, keepdims=True) # Матрица квадратов попарных расстояний внутри класса d2 = h2 + h2.T - 2.0 * (h @ h.T) # Исключаем расстояние до самой себя np.fill_diagonal(d2, np.inf) # Находим kk ближайших соседей knn = np.partition(d2, kk - 1, axis=1)[:, :kk] # Среднее расстояние до ближайших соседей внутри класса out[m] = np.mean(np.sqrt(np.maximum(knn, 0.0)), axis=1) return out def rank_score(x): """ Преобразует массив значений в ранги. Наименьший элемент получает ранг 0, следующий — 1, и так далее.

Страница 38 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

Это удобно, если потом нужно объединять несколько score разного масштаба. """ # Индексы элементов в порядке возрастания значений x order = np.argsort(x) # Массив для рангов r = np.empty_like(order, dtype=np.float64) # Записываем для каждого элемента его позицию в отсортированном порядке r[order] = np.arange(len(x), dtype=np.float64) return r [5]: # Строим outlier-score на тестовых эмбеддингах по ансамблю рангов # из трёх метрик: robust Mahalanobis + global kNN + class kNN. # Робастное расстояние Махаланобиса внутри предсказанного класса: # показывает, насколько объект нетипичен относительно распределения своего класса. s_robust = score_robust_mahalanobis(emb_test, pred_test, lam=0.15, trim_frac=0.10, iters=2) # Глобальный kNN-score: # среднее расстояние до ближайших соседей во всём наборе. # Большие значения соответствуют более изолированным точкам. s_gknn = score_global_knn(emb_test, k=10) # Class-wise kNN-score: # среднее расстояние до ближайших соседей только внутри своего предсказанного класса. # Помогает находить точки, которые странно выглядят именно внутри класса. s_cknn = score_class_knn(emb_test, pred_test, k=8) # Объединяем три score в один итоговый показатель подозрительности. # Перед объединением переводим каждый score в ранги, # чтобы разные шкалы значений не мешали друг другу. # Здесь robust Mahalanobis имеет наибольший вес, # global kNN — средний, class kNN — меньший дополнительный вклад. final_score = ( 2.0 * rank_score(s_robust) + 1.5 * rank_score(s_gknn) + 0.5 * rank_score(s_cknn) ) # Выбираем K объектов с наибольшим итоговым score # как наиболее вероятные выбросы. topk = np.argsort(-final_score)[:K] # Формируем бинарный вектор ответов: # 1 — объект считаем выбросом, 0 — обычным объектом.

Страница 39 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

is_outlier = np.zeros(n_test, dtype=np.int64) is_outlier[topk] = 1 # Собираем файл для отправки: # для каждого id указываем предсказание is_outlier. submission = pd.DataFrame({ "id": np.arange(n_test, dtype=np.int64), "is_outlier": is_outlier, }) # Сохраняем submission в CSV без индекса DataFrame. submission.to_csv(SUBMISSION_PATH, index=False) # Выводим служебную информацию: # куда сохранён файл и сколько объектов помечено как выбросы. print("Saved:", SUBMISSION_PATH) print("Predicted outliers:", int(is_outlier.sum())) # Показываем первые строки submission-таблицы. submission.head() Saved: submission.csv Predicted outliers: 1000 [5]:

0 1 2 3 4

id 0 1 2 3 4

is_outlier 0 0 0 0 0

Проверим решение: ячейка ниже вычисляет скор на public и private частях датасета. [6]: #!/usr/bin/env python3 """ Validate submission.csv against y_test.csv and compute hits@k metrics. Expected columns: y_test.csv: id, is_outlier_true, Usage submission.csv: id, is_outlier """ from __future__ import annotations import argparse import json from pathlib import Path from typing import Dict, List import pandas as pd BASELINE_SOLUTION_SCORE_PUBLIC=113 Страница 40 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

BASELINE_SOLUTION_SCORE_PRIVATE=95 AUTHOR_SOLUTION_SCORE_PUBLIC=338 AUTHOR_SOLUTION_SCORE_PRIVATE=308 def _strip_jupyter_kernel_args(unknown: List[str]) -> List[str]: cleaned: List[str] = [] i = 0 while i < len(unknown): tok = unknown[i] if tok == "-f" and i + 1 < len(unknown) and unknown[i + 1]. ,→endswith(".json"): i += 2 continue cleaned.append(tok) i += 1 return cleaned def _validate_binary_column(col: pd.Series, name: str) -> pd.Series: numeric = pd.to_numeric(col, errors="coerce") bad = numeric.isna() | (~numeric.isin([0, 1])) if bad.any(): first_bad_idx = int(col.index[bad][0]) raise ValueError( f"Колонка '{name}' должна содержать только 0/1. " f"Первая некорректная строка: {first_bad_idx}" ) return numeric.astype(int) def _clip_score_0_100(value: float) -> float: return max(0.0, min(100.0, float(value))) def _compute_hits_metrics( df: pd.DataFrame, include_score_0_100: bool = True ) -> Dict[str, float | int | bool]: k_true = int(df["is_outlier_true"].sum()) k_pred = int(df["is_outlier"].sum()) hits_at_k = int(((df["is_outlier_true"] == 1) & (df["is_outlier"] == 1)). ,→sum()) if AUTHOR_SOLUTION_SCORE <= BASELINE_SOLUTION_SCORE: normalized_score_0_100 = 0.0 else: normalized_score_0_100 = 100.0 * ( (hits_at_k - BASELINE_SOLUTION_SCORE) / (AUTHOR_SOLUTION_SCORE - BASELINE_SOLUTION_SCORE) ) normalized_score_0_100 = _clip_score_0_100(normalized_score_0_100)

Страница 41 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

metrics: Dict[str, float | int | bool] = { "n_samples": int(len(df)), "k_true": k_true, "k_pred": k_pred, "k_match": bool(k_true == k_pred), "hits_at_k": hits_at_k, } if include_score_0_100: metrics["score_0_100"] = normalized_score_0_100 return metrics def validate_and_score_submission(ytest_path: Path, submission_path: Path) -> Dict[str, object]: try: ytest = pd.read_csv(ytest_path) except Exception as exc: # pragma: no cover raise ValueError(f"Ошибка чтения ytest.csv: {exc}") from exc try: submission = pd.read_csv(submission_path) except Exception as exc: # pragma: no cover raise ValueError(f"Ошибка чтения submission.csv: {exc}") from exc if ytest.empty: raise ValueError("ytest.csv пустой") if submission.empty: raise ValueError("submission.csv пустой") required_ytest_cols = {"id", "is_outlier_true", "Usage"} required_submission_cols = {"id", "is_outlier"} missing_ytest_cols = required_ytest_cols - set(ytest.columns) if missing_ytest_cols: raise ValueError(f"В ytest.csv отсутствуют колонки: {sorted(missing_ytest_cols)}") missing_submission_cols = required_submission_cols - set(submission. columns) if missing_submission_cols: raise ValueError( f"В submission.csv отсутствуют колонки: {sorted(missing_submission_cols)}" ) ,→

ytest = ytest[["id", "is_outlier_true", "Usage"]].copy() submission = submission[["id", "is_outlier"]].copy() ytest["id"] = ytest["id"].astype(str).str.strip()

Страница 42 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

ytest["Usage"] = ytest["Usage"].astype(str).str.strip().str.title() submission["id"] = submission["id"].astype(str).str.strip() if (ytest["id"] == "").any(): raise ValueError("В ytest.csv есть пустые id") if (submission["id"] == "").any(): raise ValueError("В submission.csv есть пустые id") invalid_usage = sorted(set(ytest["Usage"]) - {"Public", "Private"}) if invalid_usage: raise ValueError(f"В ytest.csv недопустимые значения Usage: {invalid_usage}") if ytest["id"].duplicated().any(): duplicates = ( ytest.loc[ytest["id"].duplicated(keep=False), "id"] .unique() .tolist() ) suffix = " ..." if len(duplicates) > 10 else "" raise ValueError( f"В ytest.csv есть дубликаты id: {duplicates[:10]}{suffix} " f"(всего {len(duplicates)})" ) if submission["id"].duplicated().any(): duplicates = ( submission.loc[submission["id"].duplicated(keep=False), "id"] .unique() .tolist() ) suffix = " ..." if len(duplicates) > 10 else "" raise ValueError( f"В submission.csv есть дубликаты id: {duplicates[:10]}{suffix} " f"(всего {len(duplicates)})" ) y_ids = set(ytest["id"]) s_ids = set(submission["id"]) missing_ids = y_ids - s_ids if missing_ids: miss = sorted(list(missing_ids)) suffix = " ..." if len(miss) > 10 else "" raise ValueError( f"В submission.csv отсутствуют id: {miss[:10]}{suffix} " f"(всего {len(miss)})" )

Страница 43 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

extra_ids = s_ids - y_ids if extra_ids: extra = sorted(list(extra_ids)) suffix = " ..." if len(extra) > 10 else "" raise ValueError( f"В submission.csv есть лишние id: {extra[:10]}{suffix} " f"(всего {len(extra)})" ) ytest["is_outlier_true"] = _validate_binary_column(ytest["is_outlier_true"], "is_outlier_true") submission["is_outlier"] = _validate_binary_column(submission["is_outlier"], "is_outlier") merged = ytest.merge(submission, on="id", how="left", validate="one_to_one") if merged["is_outlier"].isna().any(): raise ValueError("После merge есть NaN в предсказаниях") k_true_total = int(merged["is_outlier_true"].sum()) k_pred_total = int(merged["is_outlier"].sum()) if k_pred_total != k_true_total: raise ValueError( "В submission.csv должно быть ровно K единиц в колонке is_outlier, " f"где K={k_true_total}. Сейчас: {k_pred_total}" ) overall_metrics = _compute_hits_metrics(merged, include_score_0_100=True) public_metrics = _compute_hits_metrics( merged.loc[merged["Usage"] == "Public"], include_score_0_100=False ) private_metrics = _compute_hits_metrics( merged.loc[merged["Usage"] == "Private"], include_score_0_100=False ) overall_metrics["score_0_100"] = _clip_score_0_100(overall_metrics["score_0_100"]) return { **overall_metrics, "n_total": int(len(merged)), "n_public": int((merged["Usage"] == "Public").sum()), "n_private": int((merged["Usage"] == "Private").sum()), "public": public_metrics, "private": private_metrics, } def main(argv: List[str] | None = None) -> None:

Страница 44 из 45

Заключительный этап всероссийской олимпиады школьников по информатике. Профиль «Искусственный интеллект». Второй тур. Москва, 25 марта 2026 г.

parser = argparse.ArgumentParser(description="Проверка сабмита и подсчет hits@k") parser.add_argument("--ytest", type=str, default="y_test.csv", help="Путь к y_test.csv") parser.add_argument( "--submission", type=str, default="submission.csv", help="Путь к submission.csv", ) parser.add_argument( "--save-json", type=str, default=None, help="Опциональный путь для сохранения метрик в JSON", ) args, unknown = parser.parse_known_args(argv) unknown = _strip_jupyter_kernel_args(unknown) if unknown: parser.error(f"unrecognized arguments: {' '.join(unknown)}") ytest_path = Path(args.ytest) submission_path = Path(args.submission) if not ytest_path.exists(): raise FileNotFoundError(f"файл ytest не найден: {ytest_path}") if not submission_path.exists(): raise FileNotFoundError(f"файл submission не найден: {submission_path}") metrics = validate_and_score_submission(ytest_path, submission_path) print(json.dumps(metrics, indent=2)) if args.save_json is not None: out_path = Path(args.save_json) with out_path.open("w", encoding="utf-8") as file: json.dump(metrics, file, indent=2) print(f"Метрики сохранены в JSON: {out_path}") if __name__ == "__main__": main()

Страница 45 из 45

Олимпиада по искусственному интеллекту 9 класс — другие годы и этапы

Все комплекты →