Часть 1

В мартовском номере "КвШ" я предложил вам попробовать доказать известную формулу 12+22+32+42+…+m2=(m(m+1)(2m+1))/6. Напомню, что в результате долгого путешествия мы оказались лицом к лицу с теми формулами, которые называются в математике рекуррентными.
И вот…

Задача 13. Напишите программу вычисления числа f(s,k), формула (2) для которого получена в предыдущей задаче. Придумайте несколько алгоритмов вычисления числа f(s,k). Сравните порядки операций и объемы памяти, необходимые для работы этих алгоритмов.
Посмотрим на формулу (2) с точки зрения эффективности алгоритма. Эта формула и рассуждение, которое ей предшествует, позволяют сформулировать следующий алгоритм.

АЛГ(n,k)
Дано: n монет, среди которых одна фальшивая (более легкая), и число k – количество взвешиваний каждой монеты.
Шаг 1. В столбце k определяем число s такое, что f(s-1,k)
Шаг 2. На чаши весов кладем по f(s-1,k-1) монет, а оставшиеся m монет оставляем на столе (формула (2) гарантирует нам, что на столе осталось не более f(s-1,k) монет).
Шаг 3. Если чаши находятся в равновесии, то

m=1, фальшивая монета на столе. Если m>1, то переходим к АЛГ(m, k).
В случае, когда чаши не в равновесии:
Если в легкой чашке f(s-1,k-1)=1 монет, то эта монета фальшивая.
Если f(s-1,k-1)>1, то переходим к алгоритму АЛГ(f(s-1,k-1),k-1).
Итак, рекуррентная формула дает рекурсивный алгоритм.

Задача 14. Запрограммируйте АЛГ(n,k). Продумайте интерфейс и "картинки". Например, можно рассмотреть возможность, при которой посторонний "наблюдатель" задает числа n, k и щелчком мыши выбирает фальшивую монету, а "машина" демонстрирует, как она эту монету находит.

Решения задач Задача 11. Ответ приведен в табл. 3.
Из таблицы видно, что ответом в задаче А является число 7. 20_01.jpg Задача 12. f(s,3)=(4/3) s3 - 2s2 + (8/3)s +1 А теперь приведем несколько решений задачи 13, ведь ее решение составляет фактически первый шаг алгоритма АЛГ (n,k). Решая предыдущие задачи, мы оценивали число операций в полученных алгоритмах через число взвешиваний. И это вполне естественно, ведь взвешивание в этих алгоритмах – самая трудоемкая операция.
Теперь мы будем рассматривать задачу, связанную с вычислениями. В таких задачах есть два подхода к оценке числа операций. Можно к элементарным операциям отнести любые арифметические операции (например, сложение и умножение – к операциям, одинаковым по сложности), а можно все операции переводить в операции над битами. Мы будем придерживаться первой точки зрения. Кроме того, в начале будем считать, что s намного больше k.
Решение 1 (хакерское). Запрограммируем формулу "в лоб".
Фрагмент рекурсивного алгоритма, вычисляющего f(s,k), выглядит примерно так:

Function F(s: integer; k: integer): integer;
{s>=0,k>=0}
begin
if (s=0) or (k=0) then
begin
F=1;
end
else
begin {s>=1,k>=1}
F:= 2*F(s-1,k-1)+F(s-1,k);
end;
end

Попробуем оценить время работы и объем памяти, необходимый для выполнения программы. Время работы растет экспоненциально (т. е. как функция 2s). Действительно, вычисление F(s,k) сводится к двум вызовам – F(s-1,k-1) и F(s-1,k); вычисление этих двух функций – к четырем вызовам F(s-2,k-2) F(s-1,k-1) F(s-1,k-1) F(s-2,k) затем к восьми и т. д. Заметьте, что уже на втором шаге мы будем дважды вычислять одну и ту же функцию F(s-1,k-1), т. е. проделывать лишнюю работу. То же происходит и с памятью. Если у вас рекурсия запрограммирована с помощью стека, то в стеке отложенных заданий число элементов будет расти экспоненциально, и в стеке будут появляться те же одинаковые элементы. Если вы запрограммировали рекурсию, используя ссылки, то число ссылок растет экспоненциально.
Конечно, если s и k не очень велики, то на современных компьютерах вы даже не заметите, как работает эта программа – нажали на кнопку, и результат уже на экране. Но при больших объемах вычислений это стоит учесть.

Решение 2 (программистское). Посмотрим в нашей таблице, какие значения нужно знать, чтобы вычислить f(s,k). На рис. 1 заштрихован "прямоугольник зависимости", т. е. место, где расположены значения f(x,y), принимающие участие в вычислении f(s,k).
Ясно, что компьютер априори не знает "начальных" значений f(s,0) и f(0,k). Мы можем изменить эти значения, но алгоритм должен работать.
Априори и коэффициенты 2 и 1 в формуле (2) могли бы быть другими. Все это означает, что, используя формулу (2) при построении алгоритма, нам придется хотя бы один раз вычислить каждое значение f(x,y) из "прямоугольника зависимости".
20_02.jpg Поэтому можно просто завести массив размера sk и вычислить все его элементы. Эта программа потребует и памяти порядка sk, равно как и время ее работы будет порядка sk.

Задача 15. Напишите программу, реализующую этот алгоритм.
Эта программа уже не экспоненциальная, а полиномиальная. Ресурсы (память и время), необходимые для ее работы, растут как степень от объема начальных данных (в данном случае – от s и k). Но если k и s ~ k, то и время, и память, необходимые для работы программы, будут порядка s2 (говорят, что такой алгоритм квадратичен! ).
… Если вам не надоела эта задача, то попробуйте обобщить ее, рассмотрев, например, не одну фальшивую монету, а две, три и т. д.
Похожую задачу о сортировке железнодорожных составов (тоже традиционная тема задачников для младших школьников) можно найти в статье [1]. Тем из вас, кто действительно заинтересовался алгоритмами, можно посоветовать заглянуть в замечательную книжку [2].
Следующие задачи взяты из [3].

Задачи для обобщения Задача 1. Среди 12 монет одна фальшивая, отличная по весу от остальных. Неизвестно, легче она или тяжелее. За три взвешивания найти фальшивую монету и установить, легче она остальных или тяжелее. ([4]) Задача 2. Дано по паре красных, синих и зеленых гирь, причем в каждой паре одна гиря тяжелее другой. Все тяжелые гири имеют один и тот же вес; все легкие – тоже. Требуется за два взвешивания выделить все тяжелые гири. Задача 3. Из четырех монет две – легкие (одного веса) и две – тяжелые (тоже одного веса). За сколько взвешиваний их можно разделить? Задача 4. Даны четыре монеты, две одного веса, третья тяжелее, а четвертая - легче первых двух, причем общий вес первой и второй монет равен общему весу третьей и четвертой. Сколько взвешиваний понадобится для того, чтобы выделить тяжелую и легкую монеты?



Литература [1] Кулаков А.Г. Сортировки, числа Фибоначчи, системы счисления и контекстно-свободные грамматики. Квант, № 3, 1997.
[2] Шень А. Программирование: теоремы и задачи, МЦНМО, 1995.
[3] Бронштейн Е. М. Задачи о взвешиваниях: плановый подход, УРЭК, 1995.
[4] Шклярский Д. О., Ченцов Н. Н., Яглом И. М. Избранные задачи и теоремы элементарной математики. Арифметика и алгебра. Москва, 1954.