E023. Быстрое преобразование Фурье за O (N log N). Применение к умножению двух полиномов или длинных чисел

e-maxx algorithm оригинал: C/C++ #algorithm #emaxx #fft #math #number-theory #polynomial

Источник: e-maxx.ru/algo, страница PDF 72.

Здесь мы рассмотрим алгоритм, который позволяет перемножить два полинома длиной

за время

,

что значительно лучше времени

, достигаемого тривиальным алгоритмом умножения. Очевидно, что

умножение двух длинных чисел можно свести к умножению полиномов, поэтому два длинных числа также

можно перемножить за время

. Изобретение Быстрого преобразования Фурье приписывается Кули (Coolet) и Таки (Tukey) — 1965 г. На самом деле БПФ неоднократно изобреталось до этого, но важность его в полной мере не осознавалась до появления современных компьютеров. Некоторые исследователи приписывают открытие БПФ Рунге (Runge) и Кёнигу (Konig) в 1924 г. Наконец, открытие этого метода приписывается ещё Гауссу (Gauss) в 1805 г.

Дискретное преобразование Фурье (ДПФ)

Пусть имеется многочлен

-ой степени:

Не теряя общности, можно считать, что

является степенью 2. Если в действительности

не является степенью 2, то

мы просто добавим недостающие коэффициенты, положив их равными нулю. Из теории функций комплексного переменного известно, что комплексных корней

-ой степени из единицы

существует ровно

. Обозначим эти корни через

, тогда известно, что

. Кроме того, один из этих корней

(называемый главным значением корня

-

ой степени из единицы) таков, что все остальные корни являются его степенями: . Тогда дискретным преобразованием Фурье (ДПФ) (discrete Fourier transform, DFT) многочлена

(или, что то же самое, ДПФ вектора его коэффициентов

) называются значения

этого многочлена в точках

, т.е. это вектор:

Аналогично определяется и обратное дискретное преобразование Фурье (InverseDFT).

Обратное ДПФ для вектора значений многочлена

— это вектор коэффициентов

многочлена

: Таким образом, если прямое ДПФ переходит от коэффициентов многочлена к его значениям в комплексных корнях

-

ой степени из единицы, то обратное ДПФ — наоборот, по значениям многочлена восстанавливает коэффициенты многочлена.

Применение ДПФ для быстрого умножения полиномов

Пусть даны два многочлена

и

. Посчитаем ДПФ для каждого из них:

и

— это два

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

Но это означает, что если мы перемножим вектора

и

, просто умножив каждый элемент

одного вектора на соответствующий ему элемент другого вектора, то мы получим не что иное, как ДПФ от

многочлена

: Наконец, применяя обратное ДПФ, получаем: где, повторимся, справа под произведением двух ДПФ понимается попарные произведения элементов векторов.

Такое произведение, очевидно, требует для вычисления только

операций. Таким образом, если мы

научимся вычислять ДПФ и обратное ДПФ за время

, то и произведение двух полиномов (а,

следовательно, и двух длинных чисел) мы сможем найти за ту же асимптотику. Следует заметить, что, во-первых, два многочлена следует привести к одной степени (просто дополнив коэффициенты одного из них нулями). Во-вторых, в результате произведения двух многочленов степени

получается многочлен степени

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

Быстрое преобразование Фурье

Быстрое преобразование Фурье (fast Fourier transform) — это метод, позволяющий вычислять ДПФ

за время

. Этот метод основывается на свойствах комплексных корней из единицы (а именно, на том, что степени одних корней дают другие корни). Основная идея БПФ заключается в разделении вектора коэффициентов на два вектора, рекурсивном вычислении ДПФ для них, и объединении результатов в одно БПФ.

Итак, пусть имеется многочлен

степени

, где

— степень двойки, и

: Разделим его на два многочлена, один — с чётными, а другой — с нечётными коэффициентами:

Нетрудно убедиться, что:

Многочлены

и

имеют вдвое меньшую степень, чем многочлен

. Если мы сможем за линейное время

по вычисленным

и

вычислить

, то мы и получим искомый алгоритм

быстрого преобразования Фурье (т.к. это стандартная схема алгоритма "разделяй и властвуй", и для неё

известна асимптотическая оценка

).

Итак, пусть мы имеем вычисленные вектора

и

.

Найдём выражения для

. Во-первых, вспоминая (1), мы сразу получаем значения для первой половины коэффициентов: Для второй половины коэффициентов после преобразований также получаем простую формулу:

(Здесь мы воспользовались (1), а также тождествами

,

.)

Итак, в результате мы получили формулы для вычисления всего вектора :

(эти формулы, т.е. две формулы вида

и

, иногда называют "преобразование

бабочки" ("butterfly operation"))

Тем самым, мы окончательно построили алгоритм БПФ.

Обратное БПФ

Итак, пусть дан вектор

— значения многочлена

степени

в точках

.

Требуется восстановить коэффициенты

многочлена. Эта известная задача

называется интерполяцией, для этой задачи есть и общие алгоритмы решения, однако в данном случае будет получен очень простой алгоритм (простой тем, что он практически не отличается от прямого БПФ). ДПФ мы можем записать, согласно его определению, в матричном виде:

Тогда вектор

можно найти, умножив вектор

на обратную матрицу

к матрице, стоящей слева (которая, кстати, называется матрицей Вандермонда): Непосредственной проверкой можно убедиться в том, что эта обратная матрица такова: Таким образом, получаем формулу:

Сравнивая её с формулой для

: мы замечаем, что эти две задачи почти ничем не отличаются, поэтому коэффициенты

можно находить таким

же алгоритмом "разделяй и властвуй", как и прямое БПФ, только вместо

везде надо использовать

, а

каждый элемент результата надо разделить на

. Таким образом, вычисление обратного ДПФ почти не отличается от вычисления прямого ДПФ, и его также

можно выполнять за время

.

Реализация

Рассмотрим простую рекурсивную реализацию БПФ и обратного БПФ, реализуем их в виде одной функции, поскольку различия между прямым и обратным БПФ минимальны. Для хранения комплексных чисел воспользуемся стандартным в C++ STL типом complex (определённым в заголовочном файле <complex>).

typedef complex<double> base;

void fft (vector<base> & a, bool invert) {

int n = (int) a.size();
if (n == 1)  return;

vector<base> a0 (n/2), a1 (n/2);

for (int i=0, j=0; i<n; i+=2, ++j) {

a0[j] = a[i];

a1[j] = a[i+1];

}

fft (a0, invert);

fft (a1, invert);

double ang = 2*PI/n * (invert ? -1 : 1);

base w (1), wn (cos(ang), sin(ang));

for (int i=0; i<n/2; ++i) {

a[i] = a0[i] + w * a1[i];

a[i+n/2] = a0[i] - w * a1[i];

if (invert)

a[i] /= 2, a[i+n/2] /= 2;

w *= wn;

} }

В аргумент

функции передаётся входной вектор коэффициентов, в нём же и будет содержаться результат.

Аргумент

показывает, прямое или обратное ДПФ следует вычислить. Внутри функции сначала проверяется,

что если длина вектора

равна единице, то ничего делать не надо - он сам и является ответом. Иначе вектор

разделяется на два вектора

и

, для которых рекурсивно вычисляется ДПФ. Затем вычисляется величина

,

и заводится переменная

, содержащая текущую степень

. Затем вычисляются элементы результирующего ДПФ

по вышеописанным формулам.

Если указан флаг

, то

заменяется на

, а каждый элемент результата делится на 2

(учитывая, что эти деления на 2 произойдут в каждом уровне рекурсии, то в итоге как раз получится, что все

элементы поделятся на

). Тогда функция для перемножения двух многочленов будет выглядеть следующим образом:

void multiply (const vector<int> & a, const vector<int> & b, vector<int> &

res) {

vector<base> fa (a.begin(), a.end()), fb (b.begin(), b.end());

size_t n = 1;

while (n < max (a.size(), b.size()))  n <<= 1;

n <<= 1;

fa.resize (n), fb.resize (n);

fft (fa, false), fft (fb, false);

for (size_t i=0; i<n; ++i)

fa[i] *= fb[i];

fft (fa, true);

res.resize (n);

for (size_t i=0; i<n; ++i)

res[i] = int (fa[i].real() + 0.5);

} Эта функция работает с многочленами с целочисленными коэффициентами (хотя, понятно, теоретически ничто не мешает ей работать и с дробными коэффициентами). Однако здесь проявляется проблема большой погрешности при вычислении ДПФ: погрешность может оказаться значительной, поэтому округлять числа лучше самым надёжным способом — прибавлением 0.5 и последующим округлением вниз. Наконец, функция для перемножения двух длинных чисел практически ничем не отличается от функции для перемножения многочленов. Единственная особенность — что после выполнения умножения чисел как многочлены их следует нормализовать, т.е. выполнить все переносы разрядов:

int carry = 0;
for (size_t i=0; i<n; ++i) {

res[i] += carry;

carry = res[i] / 10;

res[i] %= 10;

} (Поскольку длина произведения двух чисел никогда не превзойдёт суммарной длины чисел, то размера вектора

хватит, чтобы выполнить все переносы.)

Улучшенная реализация: вычисления "на месте"

без дополнительной памяти

Для увеличения эффективности откажемся от рекурсии в явном виде. В приведённой выше рекурсивной реализации

мы явно разделяли вектор

на два вектора — элементы на чётных позициях отнесли к одному временно созданному вектору, а на нечётных — к другому. Однако, если бы мы переупорядочили элементы определённым образом, то необходимость в создании временных векторов тогда бы отпала (т.е. все вычисления мы могли

бы производить "на месте", прямо в самом векторе

). Заметим, что на первом уровне рекурсии элементы, младшие (первые) биты позиций которых равны нулю, относятся

к вектору

, а младшие биты позиций которых равны единице — к вектору

. На втором уровне рекурсии

выполняется то же самое, но уже для вторых битов, и т.д. Поэтому если мы в позиции

каждого элемента

инвертируем порядок битов, и переупорядочим элементы массива

в соответствии с новыми индексами, то мы

и получим искомый порядок (он называется поразрядно обратной перестановкой (bit- reversal permutation)).

Например, для

этот порядок имеет вид: Действительно, на первом уровне рекурсии (окружено фигурными скобками) обычного рекурсивного алгоритма происходит разделение вектора на две части:

и

. Как мы видим, в

поразрядно обратной перестановке этому соответствует просто разделение вектора на две половинки: первые

элементов, и последние

элементов. Затем происходит рекурсивный вызов от каждой половинки; пусть результирующее ДПФ от каждой из них было возвращено на месте самих элементов (т.е. в первой и

второй половинах вектора

соответственно): Теперь нам надо выполнить объединение двух ДПФ в одно для всего вектора. Но элементы встали так удачно, что и объединение можно выполнить прямо в этом массиве. Действительно, возьмём элементы

и

, применим к

ним преобразование бабочки, и результат поставим на их месте — и это место и окажется тем самым, которое и должно было получиться:

Аналогично, применяем преобразование бабочки к

и

и результат ставим на их место, и т.д. В итоге получаем:

Т.е. мы получили именно искомое ДПФ от вектора

. Мы описали процесс вычисления ДПФ на первом уровне рекурсии, но понятно, что те же самые рассуждения верны и для всех остальных уровней рекурсии. Таким образом, после применения поразрядно обратной перестановки вычислять ДПФ можно на месте, без привлечения дополнительных массивов. Но теперь можно избавиться и от рекурсии в явном виде. Итак, мы применили поразрядно обратную перестановку элементов. Теперь выполним всю работу, выполняемую нижним уровнем рекурсии, т.е. вектор разделим на пары элементов, для каждого применим преобразование бабочки, в результате в векторе

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

на

четвёрки элементов, к каждой применим преобразование бабочки, в результате получим ДПФ для каждой четвёрки. И так далее, наконец, на последнем шаге мы, получив результаты ДПФ для двух половинок вектора

, применим к

ним преобразование бабочки и получим ДПФ для всего вектора

. Итак, реализация:

typedef complex<double> base;

int rev (int num, int lg_n) {
int res = 0;
for (int i=0; i<lg_n; ++i)
if (num & (1<<i))

res |= 1<<(lg_n-1-i);

return res;

}

void fft (vector<base> & a, bool invert) {

int n = (int) a.size();
int lg_n = 0;
while ((1 << lg_n) < n)  ++lg_n;
for (int i=0; i<n; ++i)
if (i < rev(i,lg_n))

swap (a[i], a[rev(i,lg_n)]);

for (int len=2; len<=n; len<<=1) {

double ang = 2*PI/len * (invert ? -1 : 1);

base wlen (cos(ang), sin(ang));

for (int i=0; i<n; i+=len) {

base w (1);

for (int j=0; j<len/2; ++j) {

base u = a[i+j], v = a[i+j+len/2] * w;

a[i+j] = u + v;

a[i+j+len/2] = u - v;

w *= wlen;

} } }

if (invert)
for (int i=0; i<n; ++i)

a[i] /= n;

}

Вначале к вектору

применяется поразрядно обратная перестановка, для чего вычисляется количество значащих

бит (

) в числе

, и для каждой позиции

находится соответствующая ей позиция, битовая запись которой

есть битовая запись числа

, записанная в обратном порядке. Если получившаяся в результате позиция оказалась

больше

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

Затем выполняется

стадий алгоритма, на

-ой из которых (

) вычисляются ДПФ для

блоков длины

. Для всех этих блоков будет одно и то же значение первообразного корня

, которое и

запоминается в переменной

. Цикл по

итерируется по блокам, а вложенный в него цикл по

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

Например, пусть

— уже подсчитанное число, равное обратной перестановке битов числа

. Тогда, при переходе

к следующему числу

мы должны и к числу

прибавить единицу, но прибавить её в такой

"инвертированной" системе счисления. В обычной двоичной системе счисления прибавить единицу — значит удалить все единицы, стоящие на конце числа (т.е. группу младших единиц), а перед ними поставить единицу. Соответственно, в "инвертированной" системе мы должны идти по битам числа, начиная со старших, и пока там стоят единицы, удалять их и переходить к следующему биту; когда же встретится первый нулевой бит, поставить в него единицу и остановиться. Итак, получаем такую реализацию:

typedef complex<double> base;

void fft (vector<base> & a, bool invert) {

int n = (int) a.size();
for (int i=1, j=0; i<n; ++i) {
int bit = n >> 1;
for (; j>=bit; bit>>=1)

j -= bit;

j += bit;

if (i < j)

swap (a[i], a[j]);

}

for (int len=2; len<=n; len<<=1) {

double ang = 2*PI/len * (invert ? -1 : 1);

base wlen (cos(ang), sin(ang));

for (int i=0; i<n; i+=len) {

base w (1);

for (int j=0; j<len/2; ++j) {

base u = a[i+j], v = a[i+j+len/2] * w;

a[i+j] = u + v;

a[i+j+len/2] = u - v;

w *= wlen;

} } }

if (invert)
for (int i=0; i<n; ++i)

a[i] /= n;

}

Дополнительные оптимизации

Приведём также список других оптимизаций, которые в совокупности позволяют заметно ускорить приведённую выше "улучшенную" реализацию:

● Предпосчитать реверс битов для всех чисел в некоторой глобальной таблице. Особенно легко это,

когда размер

при всех вызовах одинаков. Эта оптимизация становится заметной при большом количестве вызовов

. Впрочем, эффект от неё можно

заметить даже при трёх вызовах (три вызова — наиболее распространённая ситуация, т.е. когда требуется один раз перемножить два многочлена).

● Отказаться от использования

(перейти на обычные массивы). Эффект от этого зависит от конкретного компилятора, однако обычно он присутствует и составляет примерно 10%-20%.

● Предпосчитать все степени числа

. В самом деле, в этом цикле алгоритма раз за разом производится

проход по всем степеням числа

от

до

:

for (int i=0; i<n; i+=len) {

base w (1);

for (int j=0; j<len/2; ++j) {

[...]

w *= wlen;

} } Соответственно, перед этим циклом мы можем предпосчитать в некотором массиве все требуемые степени, и избавиться тем самым от лишних умножений во вложенном цикле. Ориентировочное ускорение — 5-10%.

● Избавиться от обращений к массивам по индексам, использовать вместо этого указатели на

текущие элементы массивов, продвигая их на 1 вправо на каждой итерации. На первый взгляд, оптимизирующие компиляторы должны быть способны самостоятельно справиться с этим, однако

на практике оказывается, что замена обращений к массивам

и

на указатели

ускоряет программу в распространённых компиляторах. Выигрыш составляет 5-10%.

● Отказаться от стандартного типа комплексных чисел

, переписав его на

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

хуже, чем с не-шаблонными типа

...

C# решение

auto-draft, проверить перед отправкой
using System;
using System.Collections.Generic;
using System.Linq;

public static class AlgorithmDraft
{
    // Auto-generated C# draft from the original e-maxx C/C++ listing. Review before production use.
    typedef complex<double> base;
    void fft (vector<base> & a, bool invert) {
            int n = (int) a.size();
            if (n == 1)  return;
            vector<base> a0 (n/2),  a1 (n/2);
            for (int i=0, j=0; i<n; i+=2, ++j) {
                    a0[j] = a[i];
                    a1[j] = a[i+1];
            }
            fft (a0, invert);
            fft (a1, invert);
            double ang = 2*PI/n * (invert ? -1 : 1);
            base w (1),  wn (cos(ang), sin(ang));
            for (int i=0; i<n/2; ++i) {
                    a[i] = a0[i] + w * a1[i];
                    a[i+n/2] = a0[i] - w * a1[i];
                    if (invert)
                            a[i] /= 2,  a[i+n/2] /= 2;
                    w *= wn;
            }
    }
    void multiply (const List<int> & a, const List<int> & b, List<int> &
    res) {
            vector<base> fa (a.begin(), a.end()),  fb (b.begin(), b.end());
            size_t n = 1;
            while (n < max (a.size(), b.size()))  n <<= 1;
            n <<= 1;
            fa.resize (n),  fb.resize (n);
            fft (fa, false),  fft (fb, false);
            for (size_t i=0; i<n; ++i)
                    fa[i] *= fb[i];
            fft (fa, true);
            res.resize (n);
            for (size_t i=0; i<n; ++i)
                    res[i] = int (fa[i].real() + 0.5);
    }
            int carry = 0;
            for (size_t i=0; i<n; ++i) {
                    res[i] += carry;
                    carry = res[i] / 10;
                    res[i] %= 10;
            }
    typedef complex<double> base;
    int rev (int num, int lg_n) {
            int res = 0;
            for (int i=0; i<lg_n; ++i)
                    if (num & (1<<i))
                            res |= 1<<(lg_n-1-i);
            return res;
    }
    void fft (vector<base> & a, bool invert) {
            int n = (int) a.size();
            int lg_n = 0;
            while ((1 << lg_n) < n)  ++lg_n;
            for (int i=0; i<n; ++i)
                    if (i < rev(i,lg_n))
                            swap (a[i], a[rev(i,lg_n)]);
            for (int len=2; len<=n; len<<=1) {
                    double ang = 2*PI/len * (invert ? -1 : 1);
                    base wlen (cos(ang), sin(ang));
                    for (int i=0; i<n; i+=len) {
                            base w (1);
                            for (int j=0; j<len/2; ++j) {
                                    base u = a[i+j],  v = a[i+j+len/2] * w;
                                    a[i+j] = u + v;
                                    a[i+j+len/2] = u - v;
                                    w *= wlen;
                            }
                    }
            }
            if (invert)
                    for (int i=0; i<n; ++i)
                            a[i] /= n;
    }
    typedef complex<double> base;
    void fft (vector<base> & a, bool invert) {
            int n = (int) a.size();
            for (int i=1, j=0; i<n; ++i) {
                    int bit = n >> 1;
                    for (; j>=bit; bit>>=1)
                            j -= bit;
                    j += bit;
                    if (i < j)
                            swap (a[i], a[j]);
            }
            for (int len=2; len<=n; len<<=1) {
                    double ang = 2*PI/len * (invert ? -1 : 1);
                    base wlen (cos(ang), sin(ang));
                    for (int i=0; i<n; i+=len) {
                            base w (1);
                            for (int j=0; j<len/2; ++j) {
                                    base u = a[i+j],  v = a[i+j+len/2] * w;
                                    a[i+j] = u + v;
                                    a[i+j+len/2] = u - v;
                                    w *= wlen;
                            }
                    }
            }
            if (invert)
                    for (int i=0; i<n; ++i)
                            a[i] /= n;
    }
                    for (int i=0; i<n; i+=len) {
                            base w (1);
                            for (int j=0; j<len/2; ++j) {
                                    [...]
                                    w *= wlen;
                            }
                    }
    int rev[MAXN];
    base wlen_pw[MAXN];
    void fft (base a[], int n, bool invert) {
            for (int i=0; i<n; ++i)
                    if (i < rev[i])
                            swap (a[i], a[rev[i]]);
            for (int len=2; len<=n; len<<=1) {
                    double ang = 2*PI/len * (invert?-1:+1);
                    int len2 = len>>1;
                    base wlen (cos(ang), sin(ang));
                    wlen_pw[0] = base (1, 0);
                    for (int i=1; i<len2; ++i)
                            wlen_pw[i] = wlen_pw[i-1] * wlen;
                    for (int i=0; i<n; i+=len) {
                            base t,
                                    *pu = a+i,
                                    *pv = a+i+len2,
                                    *pu_end = a+i+len2,
                                    *pw = wlen_pw;
                            for (; pu!=pu_end; ++pu, ++pv, ++pw) {
                                    t = *pv * *pw;
                                    *pv = *pu - t;
                                    *pu += t;
                            }
                    }
            }
            if (invert)
                    for (int i=0; i<n; ++i)
                            a[i] /= n;
    }
    void calc_rev (int n, int log_n) {
            for (int i=0; i<n; ++i) {
                    rev[i] = 0;
                    for (int j=0; j<log_n; ++j)
                            if (i & (1<<j))
                                    rev[i] |= 1<<(log_n-1-j);
            }
    }
    const int mod = 7340033;
    const int root = 5;
    const int root_1 = 4404020;
    const int root_pw = 1<<20;
    void fft (List<int> & a, bool invert) {
            int n = (int) a.size();
            for (int i=1, j=0; i<n; ++i) {
                    int bit = n >> 1;
                    for (; j>=bit; bit>>=1)
                            j -= bit;
                    j += bit;
                    if (i < j)
                            swap (a[i], a[j]);
            }
            for (int len=2; len<=n; len<<=1) {
                    int wlen = invert ? root_1 : root;
                    for (int i=len; i<root_pw; i<<=1)
                            wlen = int (wlen * 1ll * wlen % mod);
                    for (int i=0; i<n; i+=len) {
                            int w = 1;
                            for (int j=0; j<len/2; ++j) {
                                    int u = a[i+j],  v = int (a[i+j+len/2] * 1ll
    * w % mod);
                                    a[i+j] = u+v < mod ? u+v : u+v-mod;
                                    a[i+j+len/2] = u-v >= 0 ? u-v : u-v+mod;
                                    w = int (w * 1ll * wlen % mod);
                            }
                    }
            }
            if (invert) {
                    int nrev = reverse (n, mod);
                    for (int i=0; i<n; ++i)
                            a[i] = int (a[i] * 1ll * nrev % mod);
            }
    }
}

C++ решение

сопоставлено/оригинал
typedef complex<double> base;
void fft (vector<base> & a, bool invert) {
        int n = (int) a.size();
        if (n == 1)  return;
        vector<base> a0 (n/2),  a1 (n/2);
        for (int i=0, j=0; i<n; i+=2, ++j) {
                a0[j] = a[i];
                a1[j] = a[i+1];
        }
        fft (a0, invert);
        fft (a1, invert);
        double ang = 2*PI/n * (invert ? -1 : 1);
        base w (1),  wn (cos(ang), sin(ang));
        for (int i=0; i<n/2; ++i) {
                a[i] = a0[i] + w * a1[i];
                a[i+n/2] = a0[i] - w * a1[i];
                if (invert)
                        a[i] /= 2,  a[i+n/2] /= 2;
                w *= wn;
        }
}
void multiply (const vector<int> & a, const vector<int> & b, vector<int> &
res) {
        vector<base> fa (a.begin(), a.end()),  fb (b.begin(), b.end());
        size_t n = 1;
        while (n < max (a.size(), b.size()))  n <<= 1;
        n <<= 1;
        fa.resize (n),  fb.resize (n);
        fft (fa, false),  fft (fb, false);
        for (size_t i=0; i<n; ++i)
                fa[i] *= fb[i];
        fft (fa, true);
        res.resize (n);
        for (size_t i=0; i<n; ++i)
                res[i] = int (fa[i].real() + 0.5);
}
        int carry = 0;
        for (size_t i=0; i<n; ++i) {
                res[i] += carry;
                carry = res[i] / 10;
                res[i] %= 10;
        }
typedef complex<double> base;
int rev (int num, int lg_n) {
        int res = 0;
        for (int i=0; i<lg_n; ++i)
                if (num & (1<<i))
                        res |= 1<<(lg_n-1-i);
        return res;
}
void fft (vector<base> & a, bool invert) {
        int n = (int) a.size();
        int lg_n = 0;
        while ((1 << lg_n) < n)  ++lg_n;
        for (int i=0; i<n; ++i)
                if (i < rev(i,lg_n))
                        swap (a[i], a[rev(i,lg_n)]);
        for (int len=2; len<=n; len<<=1) {
                double ang = 2*PI/len * (invert ? -1 : 1);
                base wlen (cos(ang), sin(ang));
                for (int i=0; i<n; i+=len) {
                        base w (1);
                        for (int j=0; j<len/2; ++j) {
                                base u = a[i+j],  v = a[i+j+len/2] * w;
                                a[i+j] = u + v;
                                a[i+j+len/2] = u - v;
                                w *= wlen;
                        }
                }
        }
        if (invert)
                for (int i=0; i<n; ++i)
                        a[i] /= n;
}
typedef complex<double> base;
void fft (vector<base> & a, bool invert) {
        int n = (int) a.size();
        for (int i=1, j=0; i<n; ++i) {
                int bit = n >> 1;
                for (; j>=bit; bit>>=1)
                        j -= bit;
                j += bit;
                if (i < j)
                        swap (a[i], a[j]);
        }
        for (int len=2; len<=n; len<<=1) {
                double ang = 2*PI/len * (invert ? -1 : 1);
                base wlen (cos(ang), sin(ang));
                for (int i=0; i<n; i+=len) {
                        base w (1);
                        for (int j=0; j<len/2; ++j) {
                                base u = a[i+j],  v = a[i+j+len/2] * w;
                                a[i+j] = u + v;
                                a[i+j+len/2] = u - v;
                                w *= wlen;
                        }
                }
        }
        if (invert)
                for (int i=0; i<n; ++i)
                        a[i] /= n;
}
                for (int i=0; i<n; i+=len) {
                        base w (1);
                        for (int j=0; j<len/2; ++j) {
                                [...]
                                w *= wlen;
                        }
                }
int rev[MAXN];
base wlen_pw[MAXN];
void fft (base a[], int n, bool invert) {
        for (int i=0; i<n; ++i)
                if (i < rev[i])
                        swap (a[i], a[rev[i]]);
        for (int len=2; len<=n; len<<=1) {
                double ang = 2*PI/len * (invert?-1:+1);
                int len2 = len>>1;
                base wlen (cos(ang), sin(ang));
                wlen_pw[0] = base (1, 0);
                for (int i=1; i<len2; ++i)
                        wlen_pw[i] = wlen_pw[i-1] * wlen;
                for (int i=0; i<n; i+=len) {
                        base t,
                                *pu = a+i,
                                *pv = a+i+len2,
                                *pu_end = a+i+len2,
                                *pw = wlen_pw;
                        for (; pu!=pu_end; ++pu, ++pv, ++pw) {
                                t = *pv * *pw;
                                *pv = *pu - t;
                                *pu += t;
                        }
                }
        }
        if (invert)
                for (int i=0; i<n; ++i)
                        a[i] /= n;
}
void calc_rev (int n, int log_n) {
        for (int i=0; i<n; ++i) {
                rev[i] = 0;
                for (int j=0; j<log_n; ++j)
                        if (i & (1<<j))
                                rev[i] |= 1<<(log_n-1-j);
        }
}
const int mod = 7340033;
const int root = 5;
const int root_1 = 4404020;
const int root_pw = 1<<20;
void fft (vector<int> & a, bool invert) {
        int n = (int) a.size();
        for (int i=1, j=0; i<n; ++i) {
                int bit = n >> 1;
                for (; j>=bit; bit>>=1)
                        j -= bit;
                j += bit;
                if (i < j)
                        swap (a[i], a[j]);
        }
        for (int len=2; len<=n; len<<=1) {
                int wlen = invert ? root_1 : root;
                for (int i=len; i<root_pw; i<<=1)
                        wlen = int (wlen * 1ll * wlen % mod);
                for (int i=0; i<n; i+=len) {
                        int w = 1;
                        for (int j=0; j<len/2; ++j) {
                                int u = a[i+j],  v = int (a[i+j+len/2] * 1ll
* w % mod);
                                a[i+j] = u+v < mod ? u+v : u+v-mod;
                                a[i+j+len/2] = u-v >= 0 ? u-v : u-v+mod;
                                w = int (w * 1ll * wlen % mod);
                        }
                }
        }
        if (invert) {
                int nrev = reverse (n, mod);
                for (int i=0; i<n; ++i)
                        a[i] = int (a[i] * 1ll * nrev % mod);
        }
}

Java решение

auto-draft, проверить перед отправкой
import java.util.*;
import java.math.*;

public class AlgorithmDraft {
    // Auto-generated Java draft from the original e-maxx C/C++ listing. Review before production use.
    typedef complex<double> base;
    void fft (vector<base> & a, boolean invert) {
            int n = (int) a.size();
            if (n == 1)  return;
            vector<base> a0 (n/2),  a1 (n/2);
            for (int i=0, j=0; i<n; i+=2, ++j) {
                    a0[j] = a[i];
                    a1[j] = a[i+1];
            }
            fft (a0, invert);
            fft (a1, invert);
            double ang = 2*PI/n * (invert ? -1 : 1);
            base w (1),  wn (cos(ang), sin(ang));
            for (int i=0; i<n/2; ++i) {
                    a[i] = a0[i] + w * a1[i];
                    a[i+n/2] = a0[i] - w * a1[i];
                    if (invert)
                            a[i] /= 2,  a[i+n/2] /= 2;
                    w *= wn;
            }
    }
    void multiply (const ArrayList<Integer> & a, const ArrayList<Integer> & b, ArrayList<Integer> &
    res) {
            vector<base> fa (a.begin(), a.end()),  fb (b.begin(), b.end());
            size_t n = 1;
            while (n < max (a.size(), b.size()))  n <<= 1;
            n <<= 1;
            fa.resize (n),  fb.resize (n);
            fft (fa, false),  fft (fb, false);
            for (size_t i=0; i<n; ++i)
                    fa[i] *= fb[i];
            fft (fa, true);
            res.resize (n);
            for (size_t i=0; i<n; ++i)
                    res[i] = int (fa[i].real() + 0.5);
    }
            int carry = 0;
            for (size_t i=0; i<n; ++i) {
                    res[i] += carry;
                    carry = res[i] / 10;
                    res[i] %= 10;
            }
    typedef complex<double> base;
    int rev (int num, int lg_n) {
            int res = 0;
            for (int i=0; i<lg_n; ++i)
                    if (num & (1<<i))
                            res |= 1<<(lg_n-1-i);
            return res;
    }
    void fft (vector<base> & a, boolean invert) {
            int n = (int) a.size();
            int lg_n = 0;
            while ((1 << lg_n) < n)  ++lg_n;
            for (int i=0; i<n; ++i)
                    if (i < rev(i,lg_n))
                            swap (a[i], a[rev(i,lg_n)]);
            for (int len=2; len<=n; len<<=1) {
                    double ang = 2*PI/len * (invert ? -1 : 1);
                    base wlen (cos(ang), sin(ang));
                    for (int i=0; i<n; i+=len) {
                            base w (1);
                            for (int j=0; j<len/2; ++j) {
                                    base u = a[i+j],  v = a[i+j+len/2] * w;
                                    a[i+j] = u + v;
                                    a[i+j+len/2] = u - v;
                                    w *= wlen;
                            }
                    }
            }
            if (invert)
                    for (int i=0; i<n; ++i)
                            a[i] /= n;
    }
    typedef complex<double> base;
    void fft (vector<base> & a, boolean invert) {
            int n = (int) a.size();
            for (int i=1, j=0; i<n; ++i) {
                    int bit = n >> 1;
                    for (; j>=bit; bit>>=1)
                            j -= bit;
                    j += bit;
                    if (i < j)
                            swap (a[i], a[j]);
            }
            for (int len=2; len<=n; len<<=1) {
                    double ang = 2*PI/len * (invert ? -1 : 1);
                    base wlen (cos(ang), sin(ang));
                    for (int i=0; i<n; i+=len) {
                            base w (1);
                            for (int j=0; j<len/2; ++j) {
                                    base u = a[i+j],  v = a[i+j+len/2] * w;
                                    a[i+j] = u + v;
                                    a[i+j+len/2] = u - v;
                                    w *= wlen;
                            }
                    }
            }
            if (invert)
                    for (int i=0; i<n; ++i)
                            a[i] /= n;
    }
                    for (int i=0; i<n; i+=len) {
                            base w (1);
                            for (int j=0; j<len/2; ++j) {
                                    [...]
                                    w *= wlen;
                            }
                    }
    int rev[MAXN];
    base wlen_pw[MAXN];
    void fft (base a[], int n, boolean invert) {
            for (int i=0; i<n; ++i)
                    if (i < rev[i])
                            swap (a[i], a[rev[i]]);
            for (int len=2; len<=n; len<<=1) {
                    double ang = 2*PI/len * (invert?-1:+1);
                    int len2 = len>>1;
                    base wlen (cos(ang), sin(ang));
                    wlen_pw[0] = base (1, 0);
                    for (int i=1; i<len2; ++i)
                            wlen_pw[i] = wlen_pw[i-1] * wlen;
                    for (int i=0; i<n; i+=len) {
                            base t,
                                    *pu = a+i,
                                    *pv = a+i+len2,
                                    *pu_end = a+i+len2,
                                    *pw = wlen_pw;
                            for (; pu!=pu_end; ++pu, ++pv, ++pw) {
                                    t = *pv * *pw;
                                    *pv = *pu - t;
                                    *pu += t;
                            }
                    }
            }
            if (invert)
                    for (int i=0; i<n; ++i)
                            a[i] /= n;
    }
    void calc_rev (int n, int log_n) {
            for (int i=0; i<n; ++i) {
                    rev[i] = 0;
                    for (int j=0; j<log_n; ++j)
                            if (i & (1<<j))
                                    rev[i] |= 1<<(log_n-1-j);
            }
    }
    const int mod = 7340033;
    const int root = 5;
    const int root_1 = 4404020;
    const int root_pw = 1<<20;
    void fft (ArrayList<Integer> & a, boolean invert) {
            int n = (int) a.size();
            for (int i=1, j=0; i<n; ++i) {
                    int bit = n >> 1;
                    for (; j>=bit; bit>>=1)
                            j -= bit;
                    j += bit;
                    if (i < j)
                            swap (a[i], a[j]);
            }
            for (int len=2; len<=n; len<<=1) {
                    int wlen = invert ? root_1 : root;
                    for (int i=len; i<root_pw; i<<=1)
                            wlen = int (wlen * 1ll * wlen % mod);
                    for (int i=0; i<n; i+=len) {
                            int w = 1;
                            for (int j=0; j<len/2; ++j) {
                                    int u = a[i+j],  v = int (a[i+j+len/2] * 1ll
    * w % mod);
                                    a[i+j] = u+v < mod ? u+v : u+v-mod;
                                    a[i+j+len/2] = u-v >= 0 ? u-v : u-v+mod;
                                    w = int (w * 1ll * wlen % mod);
                            }
                    }
            }
            if (invert) {
                    int nrev = reverse (n, mod);
                    for (int i=0; i<n; ++i)
                            a[i] = int (a[i] * 1ll * nrev % mod);
            }
    }
}

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

Вакансии для этой задачи

Показаны активные вакансии с пересечением по тегам задачи.

Все вакансии
Активных вакансий пока нет.