MAXimal | |
добавлено: 10 Jun 2008 19:04 Содержание [скрыть] Быстрое преобразование Фурье за O (N log N). Применение к умножению двух полиномов или длинных чиселЗдесь мы рассмотрим алгоритм, который позволяет перемножить два полинома длиной Изобретение Быстрого преобразования Фурье приписывается Кули (Coolet) и Таки (Tukey) — 1965 г. На самом деле БПФ неоднократно изобреталось до этого, но важность его в полной мере не осознавалась до появления современных компьютеров. Некоторые исследователи приписывают открытие БПФ Рунге (Runge) и Кёнигу (Konig) в 1924 г. Наконец, открытие этого метода приписывается ещё Гауссу (Gauss) в 1805 г. Дискретное преобразование Фурье (ДПФ)Пусть имеется многочлен Не теряя общности, можно считать, что Из теории функций комплексного переменного известно, что комплексных корней Тогда дискретным преобразованием Фурье (ДПФ) (discrete Fourier transform, DFT) многочлена Аналогично определяется и обратное дискретное преобразование Фурье (InverseDFT). Обратное ДПФ для вектора значений многочлена Таким образом, если прямое ДПФ переходит от коэффициентов многочлена к его значениям в комплексных корнях Применение ДПФ для быстрого умножения полиномовПусть даны два многочлена Теперь, что происходит при умножении многочленов? Очевидно, в каждой точке их значения просто перемножаются, т.е. Но это означает, что если мы перемножим вектора Наконец, применяя обратное ДПФ, получаем: где, повторимся, справа под произведением двух ДПФ понимается попарные произведения элементов векторов. Такое произведение, очевидно, требует для вычисления только Следует заметить, что, во-первых, два многочлена следует привести к одной степени (просто дополнив коэффициенты одного из них нулями). Во-вторых, в результате произведения двух многочленов степени Быстрое преобразование ФурьеБыстрое преобразование Фурье (fast Fourier transform) — это метод, позволяющий вычислять ДПФ за время Основная идея БПФ заключается в разделении вектора коэффициентов на два вектора, рекурсивном вычислении ДПФ для них, и объединении результатов в одно БПФ. Итак, пусть имеется многочлен Разделим его на два многочлена, один — с чётными, а другой — с нечётными коэффициентами: Нетрудно убедиться, что: Многочлены Итак, пусть мы имеем вычисленные вектора Во-первых, вспоминая (1), мы сразу получаем значения для первой половины коэффициентов: Для второй половины коэффициентов после преобразований также получаем простую формулу: (Здесь мы воспользовались (1), а также тождествами Итак, в результате мы получили формулы для вычисления всего вектора (эти формулы, т.е. две формулы вида Тем самым, мы окончательно построили алгоритм БПФ. Обратное БПФИтак, пусть дан вектор ДПФ мы можем записать, согласно его определению, в матричном виде: Тогда вектор Непосредственной проверкой можно убедиться в том, что эта обратная матрица такова: Таким образом, получаем формулу: Сравнивая её с формулой для мы замечаем, что эти две задачи почти ничем не отличаются, поэтому коэффициенты Таким образом, вычисление обратного ДПФ почти не отличается от вычисления прямого ДПФ, и его также можно выполнять за время РеализацияРассмотрим простую рекурсивную реализацию БПФ и обратного БПФ, реализуем их в виде одной функции, поскольку различия между прямым и обратным БПФ минимальны. Для хранения комплексных чисел воспользуемся стандартным в 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; } } В аргумент Если указан флаг Тогда функция для перемножения двух многочленов будет выглядеть следующим образом: 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; } (Поскольку длина произведения двух чисел никогда не превзойдёт суммарной длины чисел, то размера вектора Улучшенная реализация: вычисления "на месте" без дополнительной памятиДля увеличения эффективности откажемся от рекурсии в явном виде. В приведённой выше рекурсивной реализации мы явно разделяли вектор Заметим, что на первом уровне рекурсии элементы, младшие (первые) биты позиций которых равны нулю, относятся к вектору Например, для Действительно, на первом уровне рекурсии (окружено фигурными скобками) обычного рекурсивного алгоритма происходит разделение вектора на две части: Теперь нам надо выполнить объединение двух ДПФ в одно для всего вектора. Но элементы встали так удачно, что и объединение можно выполнить прямо в этом массиве. Действительно, возьмём элементы Аналогично, применяем преобразование бабочки к Т.е. мы получили именно искомое ДПФ от вектора Мы описали процесс вычисления ДПФ на первом уровне рекурсии, но понятно, что те же самые рассуждения верны и для всех остальных уровней рекурсии. Таким образом, после применения поразрядно обратной перестановки вычислять ДПФ можно на месте, без привлечения дополнительных массивов. Но теперь можно избавиться и от рекурсии в явном виде. Итак, мы применили поразрядно обратную перестановку элементов. Теперь выполним всю работу, выполняемую нижним уровнем рекурсии, т.е. вектор Итак, реализация: 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; } Дополнительные оптимизацииПриведём также список других оптимизаций, которые в совокупности позволяют заметно ускорить приведённую выше "улучшенную" реализацию:
Приведём здесь реализацию с описанными улучшениями (за исключением двух последних пунктов, которые приводят к чрезмерному разрастанию кода): 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); } } На распространённых компиляторах данная реализация быстрее предыдущего "улучшенного" варианта в 2-3 раза. Дискретное преобразование Фурье в модульной арифметикеВ основе дискретного преобразования Фурье лежат комплексные числа, корни Но то же самое верно и в отношении корней Все остальные Для применения в алгоритме Быстрого преобразования Фурье нам было нужно, чтобы примивный корень существовал для некоторого Таким образом, если Последний штрих — для обратного ДПФ мы использовали вместо Таким образом, все нужные нам свойства соблюдаются и в случае модульной арифметики, при условии, что мы выбрали некоторый достаточно большой модуль Например, можно взять такие значения: модуль 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); } } Здесь функция Как показывает практика, реализация целочисленного ДПФ работает даже медленней реализации с комплексными числами (из-за огромного количества операций взятия по модулю), однако она имеет такие преимущества, как меньшее использование памяти и отсутствие погрешностей округления. Некоторые примененияПомимо непосредственного применения для перемножения многочленов или длинных чисел, опишем здесь некоторые другие приложения дискретного преобразования Фурье. Всевозможные суммыЗадача: даны два массива Например, для Построим по массивам Тогда, перемножив эти два многочлена за Всевозможные скалярные произведенияДаны два массива Инвертируем массив Поскольку все элементы Нетрудно увидеть в этой сумме, что это именно скалярное произведение вектора Решение получилось с асимптотикой Две полоскиДаны две полоски, заданные как два булевских (т.е. числовых со значениями 0 или 1) массива Эта задача фактически ничем не отличается от предыдущей задачи — задачи о скалярном произведении. Действительно, скалярное произведение двух 0/1 массивов — это количество элементов, в которых одновременно оказались единицы. Наша задача в том, чтобы найти все циклические сдвиги второй полоски так, чтобы не нашлось ни одного элемента, в котором бы в обеих полосках оказались единицы. Т.е. мы должны найти все циклические сдвиги второго массива, при которых скалярное произведение равно нулю. Таким образом, эту задачу мы решили за
|