Найти тему

Задача 262. Коммерческий калькулятор

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

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

Уже из приведённого примера следует, что надо складывать два самых маленьких числа. Это же следует и из логики: если большое число участвует в нескольких операциях сложения, то в каждой из них за это число надо будет заплатить. То есть надо стараться чаще использовать маленькие числа (ведь количество операций сложения всегда будет равно N - 1).

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

При ограничении N до 100000 поддерживать сортированный массив очень накладно по времени. Однако существует целое множество структур данных, с помощью которым можно это реализовать: куча и всевозможные деревья поиска.

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

Начнём как обычно с подключения библиотек:

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

Здесь вместо традиционной iostream я выбрал cstdio, чтобы использовать форматированный вывод, так как требуется вывести ровно два знака после запятой. А смешивать различные библиотеки ввода и вывода в одной задаче - не очень хорошая идея.

Это немного снижает удобство считывания данных, но их не так много, поэтому ничего страшного:

Считывание входных данных
Считывание входных данных

Теперь все числа лежат в multiset'е, и мы можем приступить к суммированию:

Основной цикл
Основной цикл

Цикл можно было бы написать и for, так как мы заранее знаем количество итераций. А вот тип данных для переменной sum надо выбирать осознанно, так как сумма легко может выйти за пределы int'а.

Теперь напишем функцию, которая достаёт минимальный элемент из множества (и удаляет его):

Вынимание минимального элемента из множества
Вынимание минимального элемента из множества

Минимальный элемент всегда лежит в начале, поэтому получить его значение можно через разыменование итератора. Но удалить значение просто так нельзя, так как у нас multiset, и при удалении исчезнут все числа с данным значением. Однако, если в метод erase передать итератор, то удалится только данных элемент.

iterator erase (const_iterator position);
size_type erase (const value_type& val);

Во втором случае возвращается число удалённых элементов. Помимо прочего удаление через передачу позиции удаляемого элемента работает амортизировано за константное время (а не за логарифмическое). Однако в нашем случае амортизированные оценки не подходят, потому что помимо удаления есть и добавления во множество.

Теперь дважды вызовем написанную функцию, найдём сумму и добавим её в общее множество:

Выполнение операции сложения из условия задачи
Выполнение операции сложения из условия задачи

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

Вывод ответа
Вывод ответа

Именно для функции printf мы и подключали библиотеку cstdio, чтобы удобно задать формат вывода вещественного числа.

Предыдущий выпуск: Задача 396. Точки и отрезки

А ещё проходит небольшой опрос, чтобы сделать канал лучше.

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

Наука
7 млн интересуются