Олимпиады
Задача “Предсказание”
М.С. Густокашин,
Москва
От редакции. Мы возобновляем добрую
традицию регулярных публикаций олимпиадных
материалов. Начиная с этого номера, разбор
олимпиадных задач (по одной—две в номере) будет
готовить Михаил Сергеевич Густокашин,
преподаватель ЦДО “Дистантное обучение”, г.
Москва.
Известный маг и чародей Асмодей открыл
метод предсказывать будущее. Суть метода
заключается в следующем: прохожих на улице
просят назвать случайное целое число от 1 до 10 000,
процесс прекращается, когда очередной прохожий
отказывается назвать число, затем числа
суммируются по секретной формуле и от результата
берется остаток от деления на магическое число
981 537.
Опишем секретную формулу
суммирования. Допустим, всего опросили N
человек, тогда сумма равна S = N•A1
+ (N – 1)•A2 + … + 2•AN–1 + 1•AN,
где Ai — число, сказанное i-м
человеком. Остается только посчитать остаток от
деления суммы на магическое число.
Однажды Асмодей решил предсказать
свое будущее, посчитал число, которое определяет
его будущее, схватился за сердце, посерел,
выронил листок с формулами из рук и
дематериализовался. Его ученик расшифровал
записи, просчитал свою судьбу, впал в депрессию и
ни с кем с тех пор не разговаривал. В целях
безопасности реализуйте подсчет числа,
определяющего будущее, в виде программы.
На вход дается последовательность
натуральных чисел Ai (1 Ai 10 000).
Максимальное количество чисел в
последовательности 300 000. Признаком конца
последовательности служит число 0.
Выведите остаток от деления S,
посчитанного по секретной формуле, на 981 537.
Ограничения

Пример

Комментарий к примеру
S = 10•5 + 20•4 + 30•3 + 40•2 + 50•1 = 350.
Остаток от деления 350 на 981 537 равен 350.
Решение
Решить эту задачу очень легко, если
заранее знать общее количество названных чисел:
достаточно записать их в массив, идти по нему с
конца и считать сумму всех чисел, умноженных на
их порядковый номер с конца. Однако из-за
ограничений на размер памяти сохранить их в
массив невозможно (на каждое из 300 000 чисел
требуется как минимум 2 байта, в итоге созданный
массив будет занимать слишком много места и
решение не будет удовлетворять ограничениям на
максимальный размер используемой памяти).
Другой возможный способ состоит в том,
чтобы при первом прочтении просто подсчитать
количество чисел, а затем открыть файл заново и
последовательно подсчитывать сумму, зная
коэффициент для каждого из чисел. Чтобы
участники не пользовались таким способом, в
данной задаче жюри использовало для ввода-вывода
стандартные потоки (ввод с клавиатуры и вывод на
экран), что делает возможным только однократное
чтение.
Поняв невозможность применения
простых методов, начнем искать конструктивное
решение. Причем начнем искать его “с конца”, по
аналогии с методом математической индукции. В
информатике для такого метода используется
термин “динамическое программирование”, но
способ его применения очень похож на индукцию (и
в какой-то мере на рекуррентные соотношения).
Если нам дано всего одно число, то
задача решается элементарно: ответом будет
просто это число. Теперь необходимо научиться
решать задачу для N чисел, если известен ответ
для N – 1 числа. Будем считать, что нам
известно число
SN–1 = (N – 1)•A1 + (N
– 2)•A2 + … + AN-1.
Исходя из знания SN-1 и
очередного входного числа AN,
необходимо получить SN. Рассмотрим
формулу, по которой вычисляется результат для N
чисел:
SN = N•A1 + (N –
1)•A1 + … + AN-1.
Формулы очень похожи, для наглядности
запишем их друг под другом:
SN = N•A1 + (N –
1)•A2 + … + 2•AN-1 + 1•AN
SN-1 = (N – 1)•A1 + (N
– 2)•A2 + … + 1•AN-1
Каждый коэффициент при переменных Ai
в SN отличается от соответствующего
коэффициента в SN-1 на 1. То есть
SN – SN-1 = A1
+ A2 + … + AN и SN = SN-1
+ A1 + A2 + … + AN, S0
= 0.
Таким образом, если мы будем на каждом
шаге знать сумму всех элементов без
коэффициентов, то сможем узнать значение SN,
просто прибавив эту сумму к SN-1!
А для хранения текущего значения подобной суммы
достаточно одной переменной.
Однако в реализации и этого решения
есть подводные камни. Если внимательно
посмотреть на количество и максимальный размер
каждого элемента последовательности, то можно
заметить, что искомая сумма может быть очень
большой (больше, чем 231 — максимальное
число, представимое типом longint в Паскале или
соответствующим 32-битным целым типом в другом
языке программирования). Такая же проблема
возникает и для суммы без коэффициентов — она
может превысить максимально допустимое значение
типа данных, достаточного для хранения исходных
переменных.
Здесь поможет знание следующей
формулы для суммы остатков: (A + B) mod C =
= (A mod C + B mod C) mod C. Из этой
формулы следует, что мы можем брать остаток от
деления на каждом шаге без ущерба для ответа. С
учетом всех этих моментов, решение данной задачи
на языке Паскаль будет выглядеть следующим
образом:
var
s, d, i: longint;
begin
s := 0;{сумма на предыдущем шаге с
коэффициентами}
d := 0;{сумма без коэффициентов}
repeat
read(i); {считываем очередное число}
if i <> 0 then begin
d := (d + i) mod 981537;
{добавляем новое число к сумме без
коэффициентов}
s := (s + d) mod 981537;
{добавляем сумму без коэффициентов
(уже с учетом очередного числа) к
результату}
end;
until i = 0;{дошли до конца
последовательности}
writeln(s) {выводим ответ}
end.
При реализации важно не забыть, что
последнее число 0 не входит в последовательность
(в приведенной выше реализации для этого
делается сравнение с нулем и пересчет происходит
только в случае ненулевого очередного числа).
В решении данной задачи, помимо
алгоритма, продемонстрировано, на что следует
обращать внимание при программировании
олимпиадных задач, а именно, следует учитывать
диапазон значений применяемых типов данных и
размер используемой памяти. |