Блог пользователя saprykin

Автор saprykin, 3 года назад, По-русски

Привет, Codeforces!

В этом посте я бы хотел рассказать про решение двух типов задач, с почти одинаковым решением. К сожалению я не знаю, насколько эта идея распространена. Впервые я ее придумал на региональном этапе 2021/22 года по информатике для определенной подгруппы. А на днях заметил в задаче с Google Kick Start и 1637E - Лучшая пара, поэтому решил написать данный пост.

Задача 1

Формулировка: Дано число $$$0 \leq K < 10^5$$$ и два отсортированных по возрастанию массива $$$a_1, a_2, \ldots, a_n \ (a_i \leq 10^9)$$$ и $$$b_1, b_2, \ldots, b_n \ (b_i \leq 10^9)$$$. Были выписаны $$$n^2$$$ дробей вида $$$\frac{a_i}{b_j}, 1 \leq i \leq n, 1 \leq j \leq n$$$. После этого все эти дроби были отсортированы по возрастанию. Нужно вывести значение K-ой по возрастанию дроби.

Идея: Казалось бы, генерируется $$$n^2 \leq 10^{10}$$$ чисел, и нельзя получить отсортированный массив за разумное время. Однако, если посмотреть на ограничение по $$$K$$$ можно понять, что нас интересуют не все $$$n^2$$$ чисел, а только $$$K$$$ наименьших. Тогда давайте поддерживать минимальное число и из него переходить в следующее.

Решение: Для удобства развернем массив $$$b$$$, чтобы он стал отсортирован по убыванию и обозначим за $$$(i, j)$$$ дробь $$$\frac{a_i}{b_j}$$$. Несложно заметить, что $$$(i, j) \leq (i + 1, j)$$$ и $$$(i, j) \leq (i, j + 1)$$$. Отсюда ясно, что $$$(0, 0)$$$ будет первым элементом в отсортированном массиве. Утверждение заключается в том, что если мы рассмотрели $$$(i, j)$$$ как минимальную дробь, то в "кандидаты" на новый минимум добавляется две дроби $$$(i + 1, j)$$$ и $$$(i, j + 1)$$$. Это кажется интуитивно понятным, однако чуть ниже попытка доказать, почему это так. Задача свелась к тому, чтобы поддерживать кандидатов и брать лучшего из них.

Код
Доказательство про 2 кандидатов
Доказательство асимптотики

Задача 2

Формулировка: 1637E - Лучшая пара
Идея: Заметим, что различных $$$cnt_x$$$ не более $$$\sqrt{n}$$$. Создадим массив $$$A$$$, который в индексе $$$i$$$ хранит отсортированный по убыванию вектор $$$x$$$ таких, что $$$cnt_x = i$$$. Тогда переберем $$$cnt_x, cnt_y$$$. Так как один из множителей мы зафиксировали, нам нужно максимизировать $$$x+y$$$.
Для удобства обозначим $$$a = A[cnt_x]$$$, $$$b = A[cnt_y]$$$. Позицией будем называть $$$(i, j)$$$. Нужно выбрать такую позицию, что $$$(a_i, b_j)$$$ не является запрещенной парой. Используя наблюдения, которые были высказаны выше, замечаем, что:

  • Лучший ответ в "позиции" $$$(0, 0)$$$
  • Если нас не устраивает $$$(i, j)$$$ мы переходим в $$$(i + 1, j)$$$ или $$$(i, j + 1)$$$

Это та же задача 1, но теперь мы делаем не фиксированное количество раз, а пока лучшая пара является запрещенной. Мое решение на это задачу: 146149856

Доказательство асимптотики

Задача 3

Формулировка: Вам поступило $$$N$$$ заказов напитков от друзей. Напиток задается битовой строкой $$$s_i$$$ длины $$$P$$$. Если вы купите напиток $$$S$$$ то $$$i-ый$$$ друг будет недоволен столько раз, сколько существует индексов $$$j$$$, что $$$S_j \neq s_{i, j}$$$. Однако по экономическим соображениям в магазин, в котором вы покупаете напиток, есть $$$M$$$ напитков, которые купить нельзя.
Дано: $$$1 \leq N \leq 100, 1 \leq P \leq 100, 0 \leq M \leq min(100, 2^P - 1)$$$. Вам нужно выбрать такую строку $$$S$$$, что суммарное недовольство всех друзей будет минимальным.

Оригинальные условия на английском

Идея: Аналогично задаче 2 у нас есть запрещенные множества и среди остальных нужно выбрать лучшее (в данном случае функция от множества это количество недовольств). Начальное значение определить не сложно: для каждого бита независимо смотрим, выгоднее поставить 0 или 1. Далее мы пытаемся изменить ровно один бит. Логика такая же, как и в задаче 2, но теперь не 2 перехода, а $$$P$$$. Сложность $$$O(PMlog(PM))$$$.

Код

Надеюсь данная идея поможет кому-то в решении задач.

Удачи и высокого рейтинга!

Полный текст и комментарии »

  • Проголосовать: нравится
  • +82
  • Проголосовать: не нравится

Автор saprykin, 3 года назад, По-русски

Привет, Codeforces!

В этом посте я бы хотел поделиться тем, как я пишу Дерево отрезков, объясню, почему я это делаю классом, а так же какие у этого плюсы и минусы.
Чтобы не быть голословным я бы хотел рассмотреть свое решение на конкретной задаче (осторожно, идеи к решению)

Код
Решение

Что в этом написании особенного

Основная моя идея в том, чтобы реализовать ДО как класс

Это позволяет использовать для двух деревьев отрезков более понятный интерфейс sgb.upd(l2, r2 + 1) sga.upd(l1, r1 + 1) sga.get(l1, r1 + 1) + sgb.get(l2, r2 + 1) вместо upd1(l1, r1 +1) upd2(l2, r2 + 1) и get1(l1, r1 + 1) + get2(l2, r2 + 1) А если количество деревьев переменно, то по другому это вроде не закодить(по крайней мере я не умею).

Другая не менее важная идея состоит в том, чтобы реализовать две функции каждого типа например get(int v, int l, int r, int L, int R) и get(int L, int R)

Я хочу получить значение на отрезке и не хочу думать о параметрах которые не влияют на запрос. Поэтому get(int v, int l, int r, int L, int R) является внутренним интерфейсом, который используется только для подсчета результата и не вызывается снаружи.

Так же я использую структуру, которая поддерживает приватные и публичные поля.

Это чисто эстетическое предпочтение, когда редактор подсказывает только те функции, которые могут быть вызваны снаружи.  против

Почему я пишу именно так

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

Заключение

Плюсы

  1. Помогает не думать над тем, какой параметр передавать в запрос (Например в приведенной выше задаче, в одном случае размер равен n/2 + 1 а в другом n/2. Подобная ошибка в пару символов может стоить нескольких минут, а в худшем случае и десятков).
  2. Эстетично приятно.
  3. Пишется шаблонно (сначала mrg, потом build, дальше push и тд).
  4. Легко заменить. Нужно только изменить функцию mrg и поля у вершины.
  5. Подходит для задач, где нужно одинаковое ДО для нескольких массивов

Минусы

  1. Дольше работает. Во-первых, как известно статмасивы всегда лучше векторов по скорости, однако создать класс со статмасивом лично у меня не получилось (код крашается). Во-вторых вызов функции из класса вроде работает дольше, чем обычной функции
  2. Дольше пишется.

Вывод

Объективно минусы существенные по сравнению с плюсами. Однако я считаю, что мой способ написания имеет место быть. Он помогает избежать ошибок при написании и использовании не очень внимательным людям (к которым я, конечно же, отношусь). Если решение получает TL заменить vec t; на pii t[MAXN] не составит большого труда. А функции в основном решении уже стоят где надо и с правильными параметрами.

Я никого не призываю начинать писать код по другому. Лишь хочу поделиться своими знаниями. Спасибо за внимание, и высокого рейтинга!

Полный текст и комментарии »

  • Проголосовать: нравится
  • +4
  • Проголосовать: не нравится