TGViewer
Channel Public Channel
Поступашки - Информатика

Поступашки - Информатика

@postupashki_prog

Канал посвящен олимпиадам по информатике/спортивному программированию/ЕГЭ по информатике и изучению языков.

По всем вопросам: @postupashkaProg
Чат: @botalka_prog
Subscribers
1.75K
Photos
26
Videos
1
Links
43
Recent Posts 20 shown
Post #159 612
Здравствуйте, камрады 😎
Сегодня прокачанная версия бинпоиска по ответу — параллельный бинпоиск: тот же бинпоиск, только сразу для кучи запросов.

Идея
Есть m операций (добавляем рёбра, прибавляем на отрезках) и q запросов «после какой минимальной операции для меня выполнится условие?». Условие монотонно: выполнилось после t операций — выполнится и после t + 1.

Для одного запроса это обычный бинпоиск по ответу. Но check дорогой: чтобы проверить момент t, нужно применить t операций к структуре, а это O(m). На все запросы выходит O(q · m · log m), не влезаем.

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

Раунд выглядит так:
1. Раскладываем запросы по корзинам по их mid = (lo + hi) / 2.
2. Сбрасываем структуру и применяем операции t = 1, 2, ..., m. Сразу после операции t проверяем всех, у кого mid = t, и сдвигаем им lo или hi.
3. Повторяем раунды, пока у всех не станет lo = hi.

За раунд отрезок каждого запроса уменьшается вдвое, поэтому раундов log m, и каждый запрос проверяется всего log m раз. Если бы мы проверяли все запросы после каждой операции, каждый проверился бы m раз.

Задача (Stamp Rally, AGC 002 D)
Связный граф, m пронумерованных рёбер. Запрос (x, y, z): два брата стартуют из x и y и хотят вместе посетить z вершин. Нужно минимизировать максимальный номер пройденного ребра.

Ответ — минимальное t, при котором по рёбрам 1..t из x и y вместе достижимо хотя бы z вершин. Проверяем через DSU из нашего поста: если x и y в одной компоненте, достижимо sz[root(x)], иначе сумма размеров двух компонент. Но DSU не умеет удалять рёбра, так что обычный бинпоиск пересобирал бы его на каждой проверке каждого запроса.

// root и unite с объединением по размеру — как в посте про DSU
int reach(int x, int y) {
x = root(x); y = root(y);
return x == y ? sz[x] : sz[x] + sz[y];
}

// ea[t], eb[t] — концы t-го ребра; qx, qy, qz — запросы
vector<int> lo(q, 1), hi(q, m);
while (true) {
vector<vector<int>> byMid(m + 1); // byMid[t] — у кого mid = t
bool active = false;
for (int i = 0; i < q; i++)
if (lo[i] < hi[i]) {
byMid[(lo[i] + hi[i]) / 2].push_back(i);
active = true;
}
if (!active) break; // все бинпоиски сошлись

for (int v = 1; v <= n; v++) par[v] = v, sz[v] = 1;
for (int t = 1; t <= m; t++) {
unite(ea[t], eb[t]); // добавили t-е ребро
for (int i : byMid[t]) // проверяем всех с mid = t
if (reach(qx[i], qy[i]) >= qz[i]) hi[i] = t;
else lo[i] = t + 1;
}
}
// ответ на i-й запрос — lo[i]


По раундам при m = 8: сначала у всех mid = 4. Потом запросы расходятся на [1, 4] и [5, 8] с mid = 2 и 6, и обе группы проверяются за один прогон. Потом mid = 1, 3, 5, 7. Каждый запрос проверили 3 раза вместо 8.

Важные моменты
Каждый раунд строим структуру с нуля.
Сначала применяем операцию t, потом проверяем запросы с mid = t.
Корзины вместо сортировки: раунд стоит O(n + m + q).
Если ответа может не быть, берите hi = m + 1 как фиктивное «точно выполнено». lo = m + 1 в конце значит, что ответа нет.
Приём только оффлайн, и условие обязано быть монотонным.

Асимптотика
log m раундов, каждый за O(n + m + q). Итого O((n + m + q) · log m).

Где применять
«Есть m изменений, для каждого из q объектов найдите первый момент, когда что-то выполнится»: рёбра + DSU, прибавления на отрезках + Фенвик, k-я статистика на отрезке (бинпоиск по значению + Фенвик).

Практика
Stamp Rally — AtCoder AGC 002 D
Meteors — SPOJ
Qpwoeirut and Vertices — CF 1706E

@postupashki_prog
Post #157 897
Олимпиады по информатике 2026/27: сколько теперь реально стоит диплом

Здравствуйте, камрады 😎
В посте обсудим, какие олимпиады по информатике дают льготы 10–11 классам в этом сезоне, что поменялось в перечне и почему проект Минобрнауки от 21 сентября может переписать всю олимпиадную стратегию.

Как это работает сейчас
ВсОШ стоит вне перечня: победители и призёры финала поступают без вступительных испытаний в госвузы по профилю. Школьный этап по информатике на Сириусе пройдёт 19–23 октября по профилям: программирование, ИИ, робототехника и ИБ.

Перечневые олимпиады делятся на I, II и III уровни. Что дать за диплом, решает вуз: БВИ, 100 баллов за ЕГЭ или допбаллы. Топовые вузы чаще всего дают БВИ за I уровень, а за II–III ставят 100 баллов. Льготу нужно подтвердить ЕГЭ по профилю от 75 баллов, диплом действует 4 года. Важная деталь: БВИ используется один раз, в один вуз на одну программу, а 100 баллов можно нести во все вузы, куда подаёшься.

Перечень 2026/27 по информатике
Приказ пока не подписан, поэтому всё ниже по проекту: в нём 79 олимпиад вместо 83. Прошлые перечни подписывали в конце августа, в этом году процесс затянулся.

I уровень:
— Высшая проба, МОШ, олимпиада СПбГУ
— Открытая олимпиада по программированию, Открытая олимпиада ИТМО, ИОИП (только 11 класс)
— Технокубок, Росатом и Всесибирская: все три подняли со II на I

Выпали Вузовско-академическая олимпиада УрФУ (была I уровня) и информатика КФУ (III), а у «Высшей пробы» исчез профиль «анализ данных».

Главная новость сезона
21 сентября Минобрнауки выложило проект новых правил приёма. Если его утвердят, он заработает с приёмной кампании 2027 года и затронет нынешних 11-классников:
— БВИ по перечневым олимпиадам останется только у победителей, призёры получат максимум 100 баллов.
— Засчитываться будут только дипломы за 10–11 класс.
— За четвёртый ЕГЭ, который не идёт в конкурс, дадут 15–25 допбаллов при результате от 60.

Обсуждение идёт до 5 октября, правила на 2027 год должны утвердить до 1 декабря. Причина понятна: этим летом на некоторых программах весь конкурс состоял из олимпиадников.

Что это значит на практике
Больше всех теряет призёр I уровня. Раньше это был почти гарантированный БВИ в топ, теперь только 100 баллов, к которым ещё нужно добрать остальные ЕГЭ до проходного. По правилам проведения олимпиад победители и призёры вместе составляют не больше четверти финалистов, а победителей среди них заметно меньше. Планка для БВИ резко поднимается.
ВсОШ резко дорожает. Проект касается перечня, а у ВсОШ свои правила, поэтому призёрство финала пока остаётся полноценным БВИ. Региональный этап из «ещё одной попытки» превращается в главную.
Несколько дипломов ценны не суммой, а покрытием: чем больше олимпиад в копилке, тем выше шанс, что хотя бы одна есть в списке вашего вуза.
Для десятиклассников сезон 2026/27 становится полноценной боевой попыткой, а не тренировкой.

Мое мнение
Проект ещё могут поправить, но вектор понятен: БВИ по перечню будет доставаться единицам. Выгоднее выбрать 2–3 олимпиады I уровня под свой вуз и идти именно за победой.

Самые надёжные ставки — те, что были I уровня в прошлом перечне и сохранили его в проекте: Высшая проба, МОШ, СПбГУ, обе Открытые олимпиады и ИОИП. Технокубок, Росатом и Всесибирскую повысили только в этом проекте, так что на них лучше смотреть после подписания приказа.

Бонус за четвёртый ЕГЭ айтишникам на руку: если конкурс идёт по математике, информатике и русскому, физика сверху может дать до 25 баллов. Правила приёма своего вуза ищите к 20 января.

Подписаться: @postupashki_prog
Post #155 1.29K

Forwarded from Поступашки - экономика

💻 Камрады, а вы знали, что БВИ на программную инженерию можно было получить по экономике? 💯

😌 Если вы поступаете на ИТ, кажется логичным ботать олимпиады по информатике и на этом закончить. Но в таблицах льгот есть довольно неожиданные маршруты через экономику, финансовую грамотность и анализ данных.
Например, по правилам приёма московского МИФИ-2026 победители и призёры «Высшей пробы» и «Миссии выполнима» по экономике или финансовой грамотности с результатом, полученным в 10–11 классе, при подтверждении профильной математикой от 75 могли получить БВИ на все направления и специальности, кроме 41.03.05. То есть в том числе на ПМИ, ИВТ, прикладную информатику, программную инженерию и информационную безопасность. Получается вполне рабочая схема: пишешь экономику — поступаешь на прогинж.


В МИСИС подходящих олимпиад ещё больше. На прикладную математику, ИВТ, информационные системы и технологии, прикладную информатику, системный анализ и управление и бизнес-информатику БВИ давали «Высшая проба», «Миссия выполнима», МОШ и олимпиада РАНХиГС по экономике или финграмотности. Также подходили Вернадского, «Сибириада» и олимпиада СПбГУ по экономике, Международная олимпиада по финансовой безопасности. Условия для победителей и призёров — результат за 9–11 классы и математика от 75. То есть это уже не одна случайная льгота, а отдельный набор олимпиад, который можно держать рядом с информатикой. 😄

Отдельная история — ДАНО 😜 В приёмке-2026 она шла как профиль «Анализ данных» «Высшей пробы». Например, по правилам МФТИ победители и призёры могли зачесть 100 по информатике при подтверждении информатикой от 75, а победители с математикой от 85 — получить БВИ на все конкурсные группы ФПМИ. Для обеих льгот требовался результат в олимпиаде за 11-й класс, полученный не ранее 2022 года.
Но здесь важный нюанс: в опубликованном проекте перечня на 2026/27 профиля «Анализ данных» нет. Проект — ещё не окончательный приказ, однако обещать участникам нового сезона прежние льготы пока нельзя. С ДАНО следим за обновлениями. 😘


❗️ При этом не надо считать, что любая олимпиада про финансы внезапно открывает весь ИТ. Например, «Финатлон» в том же МИФИ давал БВИ на бизнес-информатику и экономические направления, но не на ПМИ или программную инженерию. Подтверждать его нужно было обществознанием от 75. А «Высшая проба» по экономике в МФТИ давала победителям и призёрам 100 по математике при подтверждении математикой от 75, но не БВИ на ФПМИ. 😭

🔗 Короче, бросать информатику и срочно уходить ботать экономику никто не предлагает. Но если вы всё равно собираете себе несколько попыток на БВИ, экономику и финграмотность стоит хотя бы рассмотреть: это дополнительные отборы, дополнительные заклы и иногда ещё один маршрут на те же самые ИТ-направления. 💸

🔗 Именно всю эту кухню и будем разбирать в Поступашках — Экономика: какие экономические олимпиады реально стоит писать, где полезна финансовая грамотность, что происходит с ДАНО, где достаточно призёра, где нужен победитель и какие льготы действуют именно в вашем году поступления. 😎

Так что информатикам тоже советуем подписаться. Вполне может оказаться, что ещё один путь к вашему БВИ лежит вообще через экономику. 🥰
Post #154 1.81K
Здравствуйте, товарищи😎
Сегодня разбираем один из самых частотных приёмов - бинарный поиск по ответу. Если в условии есть «найдите минимальное/максимальное такое, что...», то с большой вероятностью это он.

Идея
Обычный бинпоиск ищет элемент в отсортированном массиве. Бинпоиск по ответу - про другое: мы ищем не элемент в массиве, а сам ответ в диапазоне возможных значений.

Ключевое наблюдение: часто прямую задачу «найди оптимальное значение» решать сложно, а обратную «подходит ли конкретное x?» - намного легче. Тогда мы просто перебираем x бинпоиском.

Работает это при монотонности. Пусть есть функция check(x), возвращающая true/false. Если она выглядит как FFFF...FTTT...T (сначала false, потом true) или TTTT...TFFF...F, то бинпоиском можно найти границу, где ответ меняется. Эта граница и есть ответ.

Если check(x) не монотонна - приём не работает, это первое, что нужно проверять.

Разберём на задаче
Дано n досок с длинами a[i] и число k. Нужно распилить доски на куски одинаковой целочисленной длины L так, чтобы кусков получилось хотя бы k. Найти максимальную L.

Из доски длины a[i] при длине куска L выйдет a[i] / L кусков (целочисленное деление).

Замечаем монотонность: чем меньше L, тем больше кусков. Если при некотором L набираем ≥ k кусков, то при любом меньшем - тем более. При большем L в какой-то момент кусков станет мало. Значит функция "можно ли набрать k кусков при длине L" монотонна: TTT...TFFF. Ищем последнюю L, где ещё T.

Функция check
Проверяет, хватает ли кусков при длине L:

bool check(int L, vector<int>& a, int k) {
if (L == 0) return true;
long long cnt = 0;
for (int x : a)
cnt += x / L;
return cnt >= k;
}


Обратите внимание на long long - кусков может быть очень много, в int не влезет.

Сам бинпоиск
Ищем максимальную L, при которой check вернёт true:

int solve(vector<int>& a, int k) {
int lo = 1, hi = *max_element(a.begin(), a.end());
int ans = 0;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
if (check(mid, a, k)) {
ans = mid;
lo = mid + 1;
} else {
hi = mid - 1;
}
}
return ans;
}


Логика по шагам:
1. lo и hi - границы возможного ответа. Минимум 1, максимум - самая длинная доска.
2. Берём середину mid и спрашиваем check.
3. Подходит - запоминаем ответ и идём вправо (хотим L побольше).
4. Не подходит - идём влево.
5. Границы схлопнулись, в ans лежит максимальное подходящее L.

Важные моменты
mid считаем как lo + (hi - lo) / 2, а не (lo + hi) / 2 — при больших числах сумма переполнит int.
Если ищем минимальное подходящее значение (шаблон FFF...FTTT), логика зеркальная: при true сохраняем ответ и идём влево.
Всегда проверяйте крайние случаи: ответа вообще нет, весь диапазон подходит. Обычно баги появляются именно тут.

Вещественный бинпоиск
Если ответ дробный (например, в геометрии), вместо while крутим фиксированное число итераций:

double lo = 0, hi = 1e9;
for (int iter = 0; iter < 100; iter++) {
double mid = (lo + hi) / 2;
if (check(mid))
lo = mid;
else
hi = mid;
}


100 итераций дают точность порядка 1e9 / 2^100 - с запасом на любой eps.

Асимптотика
Один check работает за O(n), бинпоиск делает O(log(max_answer)) итераций. Итого O(n log(max_answer)).

Где применять
Максимизируйте минимум / минимизируйте максимум
Распределение по k группам
Задачи со временем («за какое минимальное время успеем»)
Геометрия с вещественным ответом
Любая задача, где проверить ответ проще, чем построить

Практика
Коровы в стойла
Delivery Dilemma
Aggressive cows
Find K-th Smallest Pair Distance

@postupashki_prog
Post #151 3.02K
Появился новый бот со шпаргалками и бесплатными материалами для подготовки к ОГЭ и ЕГЭ 😱

Всё, что обычно приходится искать по разным сайтам и каналам, теперь можно найти в одном месте:

➤ Подготовка по всем предметам
➤ ИИ-помощник 24/7 (объяснит тему, решит задачу, поможет разобраться в ошибках)
➤ Конспекты, уроки и полезные материалы
➤ Возможность заниматься прямо с телефона: дома, в дороге или между уроками
➤ Готовые шпаргалки и чек-листы для повторения

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

Жмите на этот текст, чтобы перейти в бот
Post #150 2.53K
Convex Hull Trick

Сегодня обсудим одну из самых краисвых техник в алгоритмическом программировании. Convex Hull Trick - это техника оптимизации динамического программирования, которая по сути пришла из методов оптимизаций, она помогает быстрее находить лучший вариант среди множества линейных функций.

Идея здесь достаточно простая: иногда в DP для каждого состояния нужно перебрать все предыдущие состояния и выбрать минимум или максимум. Из-за этого решение может работать слишком медленно - например, за квадрат. Convex Hull Trick позволяет посмотреть на такие переходы иначе: каждое предыдущее состояние можно представить как прямую, а новый запрос — как точку, в которой нужно найти самую выгодную прямую. Главная идея в том, что не все прямые действительно нужны. Некоторые из них никогда не дадут лучший ответ, поэтому их можно спокойно выбросить. Оставшиеся прямые образуют «оболочку», по которой уже можно быстро искать ответ.

Когда это работает?
CHT обычно подходит, если переход в DP можно привести к виду:

dp[i] = лучший ответ среди выражений вида:
k[j] * x[i] + b[j]

То есть каждое предыдущее состояние j превращается в прямую:

y = k * x + b

А для текущего i мы спрашиваем: какая прямая даёт минимум или максимум в точке x[i]?

Геометрический смысл

В геометрическом смысле CHT - это хранение нижней огибающей всех прямых. Каждое предыдущее состояние DP задаёт некоторую прямую. Но не все прямые важны: если прямая нигде не является минимальной, она никогда не повлияет на ответ, и её можно удалить. В итоге мы храним только те прямые, которые хотя бы на каком-то промежутке дают лучший ответ. Эти прямые и образуют нижнюю огибающую. Запрос в точке x - это просто поиск прямой на этой огибающей, которая находится ниже всех остальных в этой точке.

Псевдокод
Этот вариант работает, если прямые добавляются в отсортированном порядке по наклону, а запросы по x идут монотонно.

hull = empty deque
function bad(line1, line2, line3):
return line2 никогда не будет лучше,
чем line1 или line3

function add_line(k, b):
new_line = (k, b)
while hull.size >= 2 and bad(hull[-2], hull[-1], new_line):
удалить последнюю прямую из hull
добавить new_line в конец hull

function get(x):
while hull.size >= 2 and value(hull[0], x) >= value(hull[1], x):
удалить первую прямую из hull
return value(hull[0], x)

function value(line, x):
return line.k * x + line.b


Для максимума знак сравнения в get нужно поменять и строить верхнюю огибающую. Если порядок добавления прямых или порядок запросов произвольный, обычный deque уже может не подойти. В таком случае часто используют Li Chao Tree.

Практика

CSES — Monster Game I
CSES — Monster Game II
CSES — Subarray Squares
AtCoder DP Contest — Z. Frog 3
Codeforces — 1083E. The Fair Nut and Rectangles

Здесь можно почитать более подробно про эту структуру

@postupashki_prog
Post #139 4.06K
Здравствуйте, товарищи, давно не виделись! 🦖
Сегодня у нас на разборе такая тема как Сжатие координат.

Идея
Часто бывает полезно преобразовать последовательность чисел либо каких-то других объектов в промежуток последовательных целых чисел. К примеру, чтобы использовать её элементы как индексы в массиве либо какой-нибудь другой структуре.

Эта задача эквивалентна нумерации элементов множества, что можно сделать за O(n) через хеш-таблицу:
vector<int> compress(vector<int> a) {
unordered_map<int, int> m;

for (int &x : a) {
if (m.count(x))
x = m[x];
else
m[x] = m.size();
}

return a;
}

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

Как вариант, можно отсортировать массив, а затем два раза пройтись по нему с хэш-таблицей. Сначала заполняем её, а во второй раз сжимаем сам массив
vector<int> compress(vector<int> a) {
vector<int> b = a;
sort(b.begin(), b.end());

unordered_map<int, int> m;

for (int x : b)
if (!m.count(x))
m[x] = m.size();

for (int &x : a)
x = m[x];

return a;
}


Также можно выкинуть из отсортированного массива дупликаты, а затем использовать его для нахождения индекса каждого элемента исходного массива бинарным поиском
vector<int> compress(vector<int> a) {
vector<int> b = a;

sort(b.begin(), b.end());
b.erase(unique(b.begin(), b.end()), b.end());

for (int &x : a)
x = int(lower_bound(b.begin(), b.end(), x) - b.begin());

return a;
}


Оба подхода работают за O(n log n)

Практика
Площадь прямоугольников
Прямоугольное деление
Закраска прямой - 2
Бал

Пишите в комментарии, какую тему нам разобрать следующей😎


@postupashki_prog
Post #137 4.33K
Здравствуйте, камрады 😭
Сегодня разбираем строки, а если быть точнее - 🇷🇺-функцию

Определение
Разберем на задаче: пусть дана строка s длины n
Z-функция от этой строки - это массив длины n, i-ый элемент которого равен наибольшему числу символов, начиная с позиции i, совпадающих с первыми символами строки s.
Если по простому, z[i] - это наибольший общий префикс строки s и её i-го суффикса.

Пример подсчитанной Z-функции
s = "aaaaa"
z[0] = 0
z[1] = 4
z[2] = 3
z[3] = 2
z[4] = 1

Реализация на С++
vector<int> z_function(string s) {
int n = s.length();
vector<int> z(n);

// L и R - границы отрезка, где мы уже были
// и знаем, что s[L...R] = s[0...R-L]
int L = 0, R = 0;

for (int i = 1; i < n; i++) {
// Если i находится внутри последнего отрезка [L, R]
if (i <= R) {
// Используем уже посчитанное значение
z[i] = min(R - i + 1, z[i - L]);
}

// Пытаемся увеличить Z-функцию
while (i + z[i] < n && s[z[i]] == s[i + z[i]]) {
z[i]++;
}

// Обновляем границы отрезка, если ушли дальше
if (i + z[i] - 1 > R) {
L = i;
R = i + z[i] - 1;
}
}

return z;
}


А где ж применять?

- Поиск подстроки в строке
- Количество различных подстрок в строке
- Сжатие строки
- Поиск палиндромов и других структур

Пример поиска подстроки
vector<int> find_pattern(const string& pattern, const string& text) {
string concat = pattern + "#" + text;
vector<int> z = z_function(concat);
vector<int> matches;

int plen = pattern.length();
for (int i = plen + 1; i < concat.length(); i++) {
if (z[i] == plen) {
matches.push_back(i - plen - 1);
}
}

return matches;
}


@postupashki_prog
Post #133 3.97K
Здравствуйте, камрады! 🦖
Сегодня смотрим на довольно важную штуку, которая поможет нафармить важнейшие баллы - стресс тестирование👍

Определение
Стресс‑тестирование - это метод поиска ошибок в коде, при котором:
1. генерируются случайные тесты;
2. результаты работы двух решений сравниваются между собой.

Обычно используют:
stupid — медленное, но гарантированно корректное решение;
smart — быстрое, но потенциально ошибочное решение.

Метод особенно полезен:
1. на соревнованиях формата IOI;
2. когда есть время на отладку;
3. если уже написано решение для маленьких подгрупп данных.

Идея
Схема стресс‑теста:
1. Генератор gen создаёт случайный корректный тест.
2. Тест подаётся на вход stupid и smart.
3. Скрипт checker сравнивает результаты.
4. Если ответы различаются - тест сохраняется, выполнение останавливается.
5. Цикл повторяется заданное число раз (или вообще бесконечно).

Разберём на примере
Задача. Дан массив чисел. Необходимо найти максимальный элемент.
Решение stupid (эталонное):
int a[maxn];

void stupid() {
int n;
cin >> n;
for (int i = 0; i < n; i++)
cin >> a[i];
int ans = 1e9;
for (int i = 0; i < n; i++)
ans = max(ans, a[i]);
cout << ans;
}


Решение smart (с ошибкой):
В цикле пропущена первая ячейка массива (i = 1 вместо i = 0):
void smart() {
int n;
cin >> n;
for (int i = 0; i < n; i++)
cin >> a[i];
int ans = 1e9;
for (int i = 1; i < n; i++) // ошибка: начинаем с i = 1
ans = max(ans, a[i]);
cout << ans;
}


Способы реализации
1. Самый простой подход: генератор и оба решения — функции внутри main.
int a[maxn];
int n;

int stupid() { /* ... */ }
int smart() { /* ... */ }

void gen() {
n = rand() % 10 + 1;
for (int i = 0; i < n; i++)
a[i] = rand();
}

int main() {
for (int i = 0; i < 100; i++) {
gen();
if (smart() != stupid()) {
cout << "WA" << endl;
cout << n << endl;
for (int j = 0; j < n; j++)
cout << a[j] << ' ';
break;
}
cout << "OK" << endl;
}
return 0;
}


Плюсы: быстро собрать для разового теста.
Минусы:
1. дублирование кода для разных задач;
2. нельзя использовать другие языки программирования;
3. усложнение исходного кода;
4. проблемы с глобальными переменными;
5. необходимость переключения режимов ввода.

2. Тестирование внешним скриптом
Решения и генератор лежат в отдельных файлах. Данные передаем через потоки ввода‑вывода.

Пишем питонячий скрипт (назовём его checker)
import os, sys

_, f1, f2, gen, iters = sys.argv

for i in range(int(iters)):
print('Test', i + 1)
os.system(f'python3 {gen} > test.txt')
v1 = os.popen(f'./{f1} < test.txt').read()
v2 = os.popen(f'./{f2} < test.txt').read()
if v1 != v2:
print("Failed test:")
print(open("test.txt").read())
print(f'Output of {f1}:')
print(v1)
print(f'Output of {f2}:')
print(v2)
break


Запуск (предварительно скомпилировав stupid и smart в ту же директорию, что и сам checker.py. При желании можно также прописать компиляцию прямо внутри скрипта.)
python3 checker.py stupid smart gen.py 100

@postupashki_prog
Post #130 4.02K
Здравствуйте, камрады 🦖
По запросам из комментариев сегодня у нас на разборе корневая декомпозиция. Разберем на примере известной задачки:
Дан массив. Нужно ответить на q запросов одного из двух типов:
1. Найти сумму на отрезке [l, r]
2. Увеличить все элементы на отрезке [l, r] на x

Сделаем вид что про Дерево Отрезков (кстати, мы его разбирали ранее!) мы не знаем

Идея
1. Делим задачу на блоки размером примерно sqrt(n)
2. Подсчитываем что-то в каждом блоке
3. Запросы обрабатываем частично через блоки, частично поштучно

Инициализация
const int maxn = 1e5, c = 330; // c ≈ √n
int a[maxn]; // исходный массив
int b[c]; // суммы по блокам
int add[c]; // отложенные прибавления для блоков

// Предподсчет сумм по блокам
for (int i = 0; i < n; i++)
b[i / c] += a[i];


1. Сумма
int sum(int l, int r) {
int res = 0;
while (l <= r) {
// Если начинаем с начала блока и он целиком в запросе
if (l % c == 0 && l + c - 1 <= r) {
res += b[l / c]; // берем сумму всего блока
l += c; // перепрыгиваем блок
} else {
res += a[l] + add[l / c]; // учитываем отложенное прибавление
l++;
}
}
return res;
}

2. Обновление
void upd(int l, int r, int x) {
while (l <= r) {
if (l % c == 0 && l + c - 1 <= r) {
b[l / c] += c * x;
add[l / c] += x;
l += c;
}
else {
b[l / c] += x;
a[l] += x;
l++;
}
}
}


Обе операции работают за O(sqrt(n))

Когда юзаем?
- Когда нужна простота реализации
- Когда дерево отрезков избыточно
- Для задач, где операции неассоциативны

Пишите в комментарии, что разобрать следующее 🦖


@postupashki_prog
Post #128 3.72K
problems.pdf188.6 KB
Друзья, вчера прошел первый этап ИОИП. Мы опубликовали разбор первого отборочного тура в нашем чате!

Контесты заключительных этапов прошлых лет можете найти здесь, также есть контесты от той же команды авторов и отборочные этапы по этой ссылке.

Небольшие рекомендации по подготовке к ИОИП и описание олимпиады:

Олимпиада проводится в ioi формате и только для 11х классов, отличная возможность получить бви в топ вузы, так как заключительный этап проводится в конце сезона (многие ребята уже на этот момент будут иметь бви и возможно не пойдут писать эту олимпиаду + задачи легче, чем обычно на олимпиадах по программированию первого уровня).

Правила отбора

Второй отборочный этап состоится 28 февраля 2026 года в 15:00 по московскому времени. Отборочные этапы рассматриваются независимо, результаты школьников, приглашенных на заключительный этап по результатам первого отборочного этапа, не влияют на отбор во втором заключительном этапе. Те, кто не прошли на заключительный этап по результатам первого отборочного этапа, могут принять участие во втором отборочном этапе.

@postupashki_prog
Post #126 3.98K
Здравствуйте, камрады 🫥
Сегодня у нас интересная тема - мосты из теории графов

Определение
Мост - это ребро, при удалении которого связный граф распадается на две части. В контексте сетей такие рёбра представляют собой уязвимости: если такое соединение выйдет из строя, часть узлов окажется недоступной.

Идея
Прямой перебор всех рёбер с проверкой связности после удаления потребует O(m ^ 2) операций, что долго для больших графов.
Упростить задачу помогает обход в глубину (DFS). При обходе все рёбра можно разделить на два типа:
- прямые рёбра, по которым DFS переходит впервые;
- обратные рёбра, которые ведут к уже посещённым вершинам.
Обратные рёбра не могут быть мостами: если удалить такое ребро, то путь между вершинами останется через дерево обхода.
Значит, достаточно проверить только прямые рёбра. Наивная проверка каждого прямого ребра отдельно всё ещё требует O(n * m) времени. Но можно оптимизировать до O(n + m), используя дополнительную информацию, которую собираем в процессе DFS.

Введём для каждой вершины v:
h[v] - глубина вершины в дереве DFS;
d[v] - минимальная глубина, достижимая из поддерева v по обратным рёбрам.

Эти величины вычисляются рекурсивно во время обхода. Для прямого ребра v -> u условие того, что оно является мостом, выглядит так: h[v] < d[u].
Если условие выполняется, то из поддерева u нет обратного ребра, ведущего в v или выше - значит, ребро критическое. В противном случае существует обходной путь, и ребро мостом не является.

Код

const int maxn = 1e5;

bool used[maxn];
int h[maxn], d[maxn];

void dfs(int v, int p = -1) {
used[v] = true;
d[v] = h[v] = (p == -1 ? 0 : h[p] + 1);
for (int u : g[v]) {
if (u != p) {
if (used[u]) // если ребро обратное
d[v] = min(d[v], h[u]);
else { // если ребро прямое
dfs(u, v);
d[v] = min(d[v], d[u]);
if (h[v] < d[u]) {
// если нельзя другим путем добраться в v или выше,
// то ребро (v, u) - мост
}
}
}
}
}

Пишите в комментарии, что разобрать следующее 🦖


@postupashki_prog
Post #125 4.25K
Здравствуйте, Камрады😋😋
Cегодня разбираем Систему Непересекающихся Множеств (СНМ), или же Disjoint Set Union (DSU)

Идея
Система Непересекающихся Множеств - это структура данных, предназначенная для работы с разбиением элементов на непересекающиеся группы. Она поддерживает две основные операции:
1. Объединить две группы в одну.
2. Определить, принадлежат ли два элемента одной группе.

Особенности
1. Каждый элемент имеет ссылку на своего "родителя"
2. Элемент, ссылающийся сам на себя, является корнем (лидером) множества
3. Два элемента находятся в одном множестве, если у них одинаковый корень
4. Для ускорения операций используются две оптимизации:
- Сжатие путей: при поиске корня все элементы на пути перенаправляются к корню
- Весовая эвристика: при объединении меньшее множество присоединяется к большему

Код (C++)

#include <bits/stdc++.h>

using namespace std;

const int MAXN = 5e5 + 10;

int dsu[MAXN];
int sz[MAXN];

int root(int v) {
    if (dsu[v] == v) return v;
    return dsu[v] = root(dsu[v]);
}

int unite(int u, int v) {
    u = root(u);
    v = root(v);
    if (u == v) {
        return 0;
    }
    if (sz[v] < sz[u]) {
        swap(u, v);
    }
    dsu[u] = v;
    sz[v] += sz[u];
    return 1;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    int n, m;
    cin >> n >> m;

    for (int i = 0; i < n; i++) {
        dsu[i] = i;
        sz[i] = 1;
    }

    for (int i = 0; i < m; i++) {
        int a, b;
        char op;
        cin >> a >> b >> op;
       
        if (op == '?') {
            if (root(a) == root(b)) {
                cout << "YES\n";
            } else {
                cout << "NO\n";
            }
        } else if (op == '+') {
            unite(a, b);
        }
    }
}


Где применять?

- Построение минимального остовного дерева (алгоритм Краскала)
- Проверка связности графа
- Нахождение компонент связности
- Добавление рёбер и проверка появления циклов
- Постепенное объединение компонент
- Задачи на оффлайн-обработку
- Объединение интервалов и отрезков
- Работа с эквивалентностями и отношениями
- Задачи на перестановки и циклические сдвиги


@postupashki_prog
Post #122 4.6K
Приветствую, камрады❤️
Сегодня разбираем Дерево Отрезков на указателях.

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

Разберем задачку
Есть массив чисел. Хотим уметь делать две операции быстро:
1. Прибавить число к одному элементу (a[k] += x)
2. Посчитать сумму на любом отрезке массива (sum(l, r))
Наивное решение (просто хранить массив) даёт O(n), а ДО - O(log n)

Код
struct Segtree {
    int l, r, sum = 0;
    Segtree *left = 0, *right = 0;
   
    Segtree(int l, int r) : l(l), r(r) {
        if (l + 1 < r) {
            int m = (l + r) / 2;
            left = new Segtree(l, m);
            right = new Segtree(m, r);
        }
    }
   
    void add(int k, int x) {
        sum += x;
        if (left) {
            if (k < left->r) left->add(k, x);
            else right->add(k, x);
        }
    }
   
    int get_sum(int ql, int qr) {
        if (ql <= l && r <= qr) return sum;
        if (qr <= l || r <= ql) return 0;
        return left->get_sum(ql, qr) + right->get_sum(ql, qr);
    }
};

Использование:
Node tree(0, n); // n — размер массива
tree.add(3, 5);  // a[3] += 5
cout << tree.get_sum(1, 7); // сумма от 1 до 6


Условия
Наша функция должна быть ассоциативной (код выше делаем складывание), т.е. чтоб можно переставлять скобки/числа без изменения результата.

@postupashki_prog
Post #121 4.14K
С наступающим, камрады. Сегодня разберём оффлайн-решение RMQ (минимум на отрезке) с помощью монотонного стека и DSU.
Задача
Дан массив a[0..n-1] и q запросов (l, r). Нужно для каждого запроса найти min(a[l..r]). Все запросы известны заранее (оффлайн).

Идея
Будем идти по правой границе r слева направо: r = 0, 1, 2, …, n-1.
В момент, когда мы дошли до позиции r, мы хотим уметь быстро отвечать на запросы, у которых правая граница равна этому r: (l, r). Если бы мы умели поддерживать для каждого l индекс минимума на отрезке [l..r], то ответ был бы просто a[minIndex].
И как раз это и будет делать DSU: после обработки r, find(l) будет возвращать индекс минимума на [l..r].
Как поддерживать минимум для всех l сразу:
Используем монотонный стек индексов. В стеке значения a по индексам идут по возрастанию (снизу вверх). Когда мы добавляем новый элемент a[r], мы выкидываем из стека все индексы x, для которых a[x] > a[r]. Получается если a[x] > a[r], то для любого отрезка, который заканчивается в r и начинается в позиции l ≤ x, элемент x уже не может быть минимумом на этом отрезке (потому что справа есть меньший a[r]). Более того, r становится “кандидатом минимума” вместо x для многих стартов l. Если просто выкидывать из стека, нам всё равно сложно понять, для каких именно l минимум “переехал” на r. Тогда с помощью DSU будем хранить переходы: если раньше минимум для l был в x, а x оказался больше нового a[r], то теперь минимум для l станет там же, где минимум для x, а это в итоге должно вести на r”. Когда индекс x вылетает из стека из-за a[r], мы делаем parent[find(x)] = r. То есть все l, которые сейчас через DSU указывают на x как на минимум, теперь должны указывать на r. После этого любой запрос (l, r) отвечается одной операцией find(l).

Тогда сгруппируем запросы по правой границе r: для каждого r храним список всех l, которые спрашивают (l, r).
Потом идём r слева направо, обновляем стек и DSU, и сразу отвечаем на все запросы с этой правой границей.



#include <bits/stdc++.h>
using namespace std;

struct Query { int l, id; };

int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);

int n, q;
cin >> n >> q;
vector<int> a(n);
for (int i = 0; i < n; i++) cin >> a[i];

// запросы сгруппированы по правой границе
vector<vector<Query>> byR(n);
for (int id = 0; id < q; id++) {
int l, r;
cin >> l >> r;
if (l > r) swap(l, r);
// если вход 1-based, то сделайте: --l; --r;
byR[r].push_back({l, id});
}

vector<int> parent(n), ans(q);

function<int(int)> findp = [&](int v) -> int {
if (parent[v] == v) return v;
return parent[v] = findp(parent[v]);
};

vector<int> st; // монотонный стек индексов
st.reserve(n);

for (int r = 0; r < n; r++) {
parent[r] = r; // минимум на [r..r] это r

// выкидываем всё, что больше текущего значения
while (!st.empty() && a[st.back()] > a[r]) {
parent[findp(st.back())] = r;
st.pop_back();
}
st.push_back(r);

// отвечаем на запросы, у которых правая граница равна r
for (auto [l, id] : byR[r]) {
ans[id] = a[findp(l)];
}
}

for (int i = 0; i < q; i++) {
cout << ans[i] << "\n";
}
}


Каждый индекс один раз добавляется в стек и один раз из него удаляется, значит работа со стеком O(n). DSU с сжатием путей даёт почти O(1) (обратная аккермана от n) на find, итого O((n + q) · alpha(n)).

@postupashki_prog
Post #118 4.37K
Здравствуйте, камрады😎💪
Сегодня разберём мощный и крайне полезный инструмент оптимизации в алгоритмах - битовые маски и битовые структуры данных.

Идея
Если у нас есть множество чисел, то его можно представить как булев массив, где 1 означает присутствие элемента, а 0 — отсутствие.
Но процессоры работают не с отдельными битами, а целыми машинными словами (обычно 64 бита).
Поэтому можно группировать 64 булевых значений в одно число и делать 64 операций за один такт.

Это называется битовое сжатие, и оно ускоряет алгоритмы примерно в 64 раза.

std::bitset
В C++ уже есть готовая структура -  std::bitset, которая работает как большое двоичное число:

bitset<lim> b;
b.set();
b.reset();
b.flip();
b.count();
cout << b[42];


Поддерживает все операции: &, |, ^, ~, <<, >>.
Удобно, просто и очень быстро.

Задача 1: Задача о рюкзаке
Классическое решение работает за
O(n ⋅ m):
bool dp[m] = {};
dp[0] = 1;
for (int i = 0; i < n; i++)
    for (int x = m - a[i]; x >= 0; x--)
        dp[x + a[i]] |= dp[x];


Но с битсетами - всего O(n * m / w):
bitset<m> b;
b[0] = 1;
for (int i = 0; i < n; i++)
    b |= b << a[i];


Ускорение - в 30–60 раз. А мы по сути ничего толком и не меняли😎😎😎

Задача 2: Проверка цикла длины 3
Пусть есть ориентированный граф (матрица смежности).
Нужно проверить, существует ли цикл вида a -> b -> c -> a.
С битсетами решение работает за
O(n³ / w):
bitset<maxn> g[maxn];
for (int a = 0; a < n; a++) {
    for (int b = 0; b < n; b++) {
        if (g[a][b] && (~g[a] & g[b]).any()) {
            // найден цикл длины 3
        }
    }
}


Задача 3: Перемножение матриц
Если считаем не количество путей, а только факт достижения, достаточно побитового умножения:
typedef bitset<maxn> t;
typedef array<t, maxn> matrix;

matrix matmul(matrix a, matrix b) {
    matrix c;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            if (a[i][j])
                c[i] |= b[j];
    return c;
}


Работает намного быстрее обычного умножения

@postupashki_prog
Post #115 23.6K
Разбор_олимпиады_РОСАТОМ_по_информатике.pdf132.6 KB
Разобрали для вас отборочный этап росатом, сегодня последний день отбора, поэтому регистрируемся смотрим идеи в решениях и аккуратно переписываем отбор.

Не забываем, что сейчас идёт осенняя школа по олимпиадному программированию и по прмокоду "РОСАТОМ", вы получите дополнительную скидку на полный 3х месячный курс 13200 8000.

Также если наберётся 100 репостов, сделаем разбор следующего этапа шаг в будущее😎

@postupashki_prog
Post #113 25.4K
Разбор_олимпиады_Изумруд_по_информатике.pdf141.3 KB
Друзья, публикуем разбор отборочного этапа олимпиады Изумруд. Олимпиада максимально тривиальная (задачи просто типовые егэ из банка), проходные баллы в финалы обычно от ~60, даёт возможность получить 100 баллов в вузы второго тиража при поступлении, решаем и забираем страховочный билет в универ)

Не забываем, что сейчас идёт осенняя школа по олимпиадному программированию и по прмокоду "ИЗУМРУД", вы получите дополнительную скидку на полный 3х месячный курс 13200 8000.

Также если наберётся 100 репостов, сделаем разбор отборочного этапа РОСАТОМ😎

@postupashki_prog
Post #112 5.87K
Здравствуйте, камрады😎

Сегодня у нас на разборе топологическая сортировка. Алгос, который может быть полезен при решении задач на графы.

Идея
Если коротко: у нас есть ориентированный граф, и нам нужен такой порядок вершин, чтобы все рёбра шли из более ранней вершины в более позднюю.
Другими словами, чтобы никакая задача не “опережала” свою зависимость.

Особенности
Граф с циклом не сортируется
Как ни расставляй вершины массива, по ребрам цикла невозможно идти строго “вправо”.

Ациклический граф всегда можно отсортировать
В нём всегда есть вершина без исходящих рёбер — её можно поставить последней.
Убираем такие вершины по одной, пока граф не пуст — получаем массив, который потом разворачиваем.

Реализация
Реализация проще через DFS:
Выходим из вершины, у которой нет новых исходящих рёбер;
Продолжаем из тех вершин, исходящие ребра которых ведут только в уже посещённые вершины.
Записываем вершины в порядке выхода из DFS и разворачиваем массив — получаем корректную топологическую сортировку.

const int maxn = 1e5;
bool used[maxn];
vector<int> t;

void dfs(int v) {
used[v] = true;
for (int u : g[v])
if (!used[u])
dfs(u);
t.push_back(v);
}

void topological_sort() {
for (int v = 0; v < n; v++)
if (!used[v])
dfs(v);
reverse(t.begin(), t.end());
}

Применение
Топологическую сортировку можно использовать для проверки достижимости
Если вершина a идёт позже вершины b в массиве — значит, из a до b достичь нельзя.
Однако обратное не гарантируется — a может быть достижима из b или нет.

Задачки
Course Schedule
Course Schedule II
Longest Increasing Path in a Matrix
Sort Items by Groups Respecting Dependencies
Maximum Employees To Be Invited To A Meeting

Ещё больше разборов и подборок задач на нашей 3х месячной олимпиадной смене по информатике

@postupashki_prog
Post #109 5.03K
Здравствуйте, Товарищи!!!😎😎😎
Сегодня разбираем один из важнейших алгоритмических приёмов — метод сканирующей прямой (или просто scanline).
Этот подход довольно часто встречается в задачах на геометрию, интервалы, события и т. д.
На олимпиаде ШВБ по информатике одна задачек решалась именно этим алгосом, так что смотрим и запоминаем!!😎

Определение & Идея
Вместо того чтобы проверять каждую точку или отрезок по отдельности, мы сортируем все интересные события (начала, концы отрезков, запросы и т.д.) и проходим по ним слева направо, поддерживая нужные параметры.
Изменения происходят только в "интересных точках" — началах и концах отрезков.

Разберем парочку известных задачек
Задача 1
Дано n отрезков [l_i, r_i]. Найти точку, покрытую максимальным числом отрезков.

Идея
Каждое начало отрезка — событие +1, конец — -1.
Сортируем события по координате (при равенстве сначала начала).
Идём слева направо, поддерживая cnt — текущее число активных отрезков.
И получаем максимум cnt — это наш ответ.
struct event { int x, type; };

int scanline(vector<pair<int, int>> segments) {
    vector<event> events;
    for (auto [l, r] : segments) {
        events.push_back({l, 1});
        events.push_back({r, -1});
    }

    sort(events.begin(), events.end(), [](event a, event b) {
        return (a.x < b.x || (a.x == b.x && a.type > b.type));
    });

    int cnt = 0, res = 0;
    for (auto e : events) {
        cnt += e.type;
        res = max(res, cnt);
    }
    return res;
}

По сложности вышло O(n log n). Как вы видите, алгос не супер сложный

Задача 2
Найти суммарную длину объединения всех [l_i, r_i]

Идея
Как и раньше, отсортируем события и пойдём слева направо:
Если cnt > 0, добавляем к ответу длину между текущей и предыдущей координатой;
При входе в отрезок cnt++, при выходе — cnt--.
Работает за O(n log n)
int cnt = 0, res = 0, prev = -inf;
for (auto e : events) {
    if (prev != -inf && cnt > 0)
        res += e.x - prev;
    cnt += e.type;
    prev = e.x;
}


Итоги
Как вы видите, сканлайн это:
Довольно универсальный алгос для решения задачек
Объединяет подходы из ДП, геомы и др.


Практика (LeetCode)
Count Days Without Meetings
Maximum Beauty of an Array After Applying Operation
Shifting Letters II
Two Best Non-Overlapping Events
Merge Intervals (обязательно прорешайте эту задачку!!! похожая формулировка была на отборе ШВБ + иногда дают на алгособесе в Яндекс)
Interval List Intersections

Ещё больше разборов и подборок задач на нашей 3х месячной олимпиадной смене по информатике

@postupashki_prog
Older posts →

About this channel

How can I read @postupashki_prog without a Telegram account?
TGViewer shows the public web preview Telegram publishes for Поступашки - Информатика: recent posts, photos, videos and the subscriber count, with no app, login or account.
How many subscribers does Поступашки - Информатика have?
Поступашки - Информатика (@postupashki_prog) has 1.75K subscribers on Telegram, refreshed roughly every 30 minutes.
Does Поступашки - Информатика know I viewed it here?
No. Public channel previews carry no viewer identity, and TGViewer has no accounts or tracking of what you look up.
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →