Найти тему

Задача 396. Точки и отрезки

Классическая несложная задача, которая имеет много разных решений, но почему-то оценена как довольно сложная. Давайте читать условие:

Условие задачи с сайта acmp.ru
Условие задачи с сайта acmp.ru

Мы видим довольно большие входные данные: количество отрезков и точек до ста тысяч. То есть нельзя просто для каждой точки проверить все отрезки и посчитать. Даже если отсортировать отрезки по левому концу и считать только до того, который начинается правее заданной точки, тоже не подходит.

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

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

Например в нашей задаче можно для каждого события "начало отрезка" прибавлять единицы к некоторому счётчику, а для событий "конец отрезка" - вычитать единицу. Тогда в момент события "точка" в счётчике как раз будет ответ на задачу - количество открытых отрезков, которые ещё не закрылись.

Красота и простота этого решения ломается тем, что в один вектор надо сложить разнородные данные - отрезки и точки. Поэтому надо добавлять разные поля, помечающие, что это за событие.

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

Поэтому предлагаю сделать небольшую модификацию решения:

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

Это уменьшает количество типо событий и надо лишь следить, чтобы точки, соответствующие началам отрезков при сортировке оказались раньше, чем интересующие нас точки.

Данных довольно много, поэтому лучше писать на C++. Хоть на Python и можно сдать эту задачу, это сделал лишь один человек.

Подключение необходимых библиотек
Подключение необходимых библиотек

Подключим библиотеки. Нам понадобятся вектор, сет и алгоритм сортировки.

Ввод данных
Ввод данных

Создадим вектора для отрезков и для событий. Оба они будут содержать пары. Для отрезков, понятно, что это начало и конец отрезка. А в векторе событий будем хранить координату и номер отрезка или точки. Чтобы их отличать, номера отрезков будет хранить со знаком "-". Именно это и даст нам нужный порядок при сортировке событий в одной точке.

Считывание отрезков
Считывание отрезков

В 16-ой строке проверяем, чтобы первое число действительно было левым концом отрезка, потому что в условии задачи это не гарантируется. И, при необходимости, меняем концы местами. Если убрать эту проверку, то получите неверный ответ на 6 тест.

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

Считывание координат точек
Считывание координат точек

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

Сортировка событий
Сортировка событий

Заведём сет для хранения правых концов отрезков и вектор для хранения ответов и пойдём по всем событиям по порядку:

Обработка всех событий
Обработка всех событий

Тогда возможны две ситуации:

  • либо это начало некоторого отрезка (то есть второе значение в паре меньше нуля и по модулю равно номеру отрезка),
  • либо это заданная точка (и второе значение в паре - это её номер).
Обработка отрезка
Обработка отрезка

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

В случае точки, надо сначала выкинуть из сета все отрезки, которые уже закрыты (потому что у нас нет отдельного события на закрытие отрезка). И тогда все оставшиеся отрезки и будут теми, которым принадлежит точка:

Обработка точки
Обработка точки

И в ответ кладём их количество.

Вывод вектора с ответами:

Вывод ответа и завершение программы
Вывод ответа и завершение программы

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

Предыдущий выпуск: Задача 229. Двухтуровая олимпиада

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