В школе изучался неэффективный метод умножения «в столбик»
A(x)⋅B(x)=k=0∑nxki+j=k∑aibj
Этот метод требует (n+1)(n+2)/2=Θ(n2) умножений.
Каждый многочлен P(x)=k=0∑nak⋅xk степени n может быть представлен списком своих n+1 коэффициентов a0,a1,…,an,
а может быть представлен списком значений в n+1 точках (x0,P(x0)),(x1,P(x1)),…,(xn,P(xn)).
Если два многочлена представлены значениями, а не коэффициентами, то их произведение считается быстро.
Пусть многочлены A(x) и B(x) имеют в точках x0,x1,…,x2n+1 значения A0,A1,…,A2n+1 и B0,B1,…,B2n+1.
Тогда многочлен A(x)⋅B(x) имеет в этих точках значения A0B0,A1B1,…,A2n+1B2n+1.
Такой способ вычисления требует 2n+1=O(n) умножений.
Получаем следующий код
inputconstint n
inputarray[int] a[n+1]inputarray[int] b[n+1]array[int] x[2*n+1]=[???,???,...,???]array[int] A[2*n+1]=evaluate(a, x)array[int] B[2*n+1]=evaluate(b, x)array[int] C[2*n+1]forint i =0; i <2*n+1; i++:
C[i]= A[i]* B[i]array[int] c[2*n+1]=interpolate(x, C)
Нам осталось придумать эффективную реализацию операций evaluate(p, x) — вычисление значений многочлена,
заданного коэффициентами p, в точках x и interpolate(x, P) — восстановление коэффициентов многочлена
по его значениям P в точках x.
Стандартными методами можно реализовать
операцию evaluate(p, x) за n+1 умножений с помощью схемы Горнера
и операцию interpolate(x, P) за O(n3) методами обычной интерполяции.
Можно, конечно, попробовать оптимизировать интерполяцию до O(n2), но это тоже не подходит,
потому что школьный метод всё еще эффективнее.
Мы вольны выбирать узлы xj, и эта способность является ключом к построению эффективного алгоритма.
Дискретное преобразование Фурье
Выберем число N⩾n+1, чтобы иметь запас на будущее
(мы из двух многочленов степени n хотим сделать многочлен степени 2n)
и чтобы было проще считать.
Для того, чтобы все операции делать эффективно, можно выбрать узлы xj следующим образом
xj=e2πij/N=wj
где w=e2π/N — первый комплексный корень из единицы.
Дискретное преобразование Фурье многочлена P(x),
заданного коэффициентами P(x)=j=0∑N−1pj⋅xj,
называется вычисление его значений в N точках w0,w1,w2,…,wN−1.
При этом список коэффициентов a0,a1,a2,…,an дополнен нулями до длины n.
Можно сформулировать задачу поиска дискретного преобразования Фурье в матричных терминах
Алгоритм Кули-Тьюки находит дискретного преобразования Фурье
многочлена P(x)=j=0∑npj⋅xj.
Перед всеми манипуляциями нам надо дополнить массив коэффициентов pj нулями так,
чтобы его длина равнялась степени двойки.
То есть, если исходная степень многочлена P(x) равнялась n,
то после дополнения его степень будет N=2⌈log2n⌉,
а коэффициенты pj=0 при j>n.
Исходный многочлен теперь выглядит так
P(x)=j=0∑N−1pj⋅xj
Представим многочлен в виде P(x)=S(x2)+x⋅T(x2),
где S(x) состоит из коэффициентов при чётных степенях P(x),
а T(x) состоит из коэффициентов при нечётных степенях P(x).
S(x)=j=0∑N/2−1p2j⋅xjT(x)=j=0∑N/2−1p2j+1⋅xj
При N=2k можно использовать равенство
w2j=w2jmod2k=w2⋅(jmodk)
С помощью этого можно разделить задачу вычисления дискретного преобразования Фурье P(x) к двум вдвое меньшим задачам
functionfft(array[int] p[N],complex w)->array[N]:if N ==1:return P
constint k = N /2array[int] s[k], t[k]forint i =0; i < N; i++:if i %2==0:
s[i/2]= p[i]else:
t[i/2]= p[i]
S =fft(s, w * w)
T =fft(t, w * w)array[int] result[N]complex wt =1forint i =0; i < N; i++:
result[i]= S[i % k]+ wt * T[i % k]
wt *= w
return result
Другие основания
Что нам нужно было от wj?
Только то, что все они являются корнями из единицы, то есть образуют циклическую группу порядка n.
Мы выбрали первый попавшийся объект —
дискретную подгруппу группы C/R, имеющую порядок n.