Теоретический материал: алгоритм Карацубы и быстрое преобразование Фурье (А.Климовский)
Алгоритм Карацубы
Идея, сложность
Алгоритм по своей сути рекурсивный и основывается на сведении задачи перемножения многочленов степени 2n к трем задачам умножения многочленов степени n:
(1)
Вычисляются перемножением многочлены ,
,
. Последний в свою очередь равен
. Таким образом, вычитанием найдем и второе выражение в правой части формулы (1).
Оценим получающуюся сложность
, где в
включены все производимые вычитания и сложения многочленов.
Итого, как это строго доказано в [1],
Реализация, оптимизации
С реализацией проблем возникнуть не должно. Ключевой оптимизацией является использование «наивного» алгоритма для маленьких значений n. Это значение, после которого n называется «маленьким» зависит от разных факторов. В качестве упражнения рекомендуется дома его узнать для своего компьютера, а также для популярных тестирующих систем on-line. Еще в реализациях часто используется много операций копирования, обнуления и выделения памяти. Это вполне можно свести к минимуму.
Приведем отрывки кода (взятые из авторского решения первой из разбираемых задач), достаточно оптимально реализующего описанный алгоритм на языке Delphi.
type tpoly=array [0..MaxD] of integer;
ppoly=^tpoly;
procedure add (var a:tpoly; const b:tpoly; bd:integer; const c:tpoly; cd:integer);
var i:integer;
begin
for i:=0 to min (bd, cd) do
a[i]:=b[i]+c[i];
if bd<cd then
move (c[bd+1], a[bd+1], (cd-bd)*sizeof (integer))
else
move (b[cd+1], a[cd+1], (bd-cd)*sizeof (integer))
end;
procedure mul (var a:tpoly; const b, c:tpoly; d:integer);
var i, j, k:integer;
x, y, z:array of integer;
begin
if d<50 then begin
for i:=0 to d do
for j:=0 to d do
inc (a[i+j], b[i]*c[j]);
exit;
end;
k:=d shr 1 + 1;
mul (a, b, c, k-1);
mul (ppoly (@a[k shl 1])^, ppoly (@b[k])^, ppoly (@c[k])^, d- k);
setlength (x, k);
add (ppoly (@x[0])^, b, k-1, ppoly (@b[k])^, d-k);
setlength (y, k);
add (ppoly (@y[0])^, c, k-1, ppoly (@c[k])^, d-k);
setlength (z, (k-1) shl 1 + 1);
mul (ppoly (@z[0])^, ppoly (@x[0])^, ppoly (@y[0])^, k-1);
sub (ppoly (@z[0])^, ppoly (@z[0])^, (k-1) shl 1, ppoly (@a[0])^, (k-1) shl 1);
sub (ppoly (@z[0])^, ppoly (@z[0])^, (k-1) shl 1, ppoly (@a[k shl 1])^, (d-k) shl 1);
add (ppoly (@a[k])^, ppoly (@a[k])^, (k-1) shl 1, ppoly (@z[0])^, (k-1) shl 1);
end;
Видно, что этот код довольно некрасив, и хотя идейно прост, может уйти много времени на написание и отладку. К сожалению, здесь нельзя сказать, что такие оптимизации обычно не нужны, потому что если в задаче требуется алгоритм Карацубы, то ограничения чаще всего достаточно жесткие.
И напоследок не забудьте про то, что числа ― это не совсем многочлены. Типичной ошибкой является неправильный выбор основания системы счисления. Основание системы счисления, которое не вызовет переполнение используемых типов в некоторых реализациях может зависеть от длины перемножаемых чисел. Рекомендуется избегать таких реализаций, это возможно без больших потерь производительности.