算法课的 project 中有一道挺有意思的题,原文如下:
English Version Formula Computing
Description Given two arrays $a$ and $b$ of length $n$ which contains positive integers less than or equal to 100 (we assume that the array index starts at zero and $n < 10^5$), calculate $C[k]=\sum_{i=k}^{n-1}(a[i]\times b[i - k])$ for $k = 0,1,…,n-1$.
The first line contains a positive integer $n$. There are two positive integers in each of the next $n$ lines representing the elements in the two arrays, i.e., the $(i+2)\text{-th}$ line contains $a[i]$,$b[i]$.
Output $n$ lines. The $k\text{-th}$ line contains one positive number which represents $C[k-1]$.
Sample Solution Naive Method 很简单,二重循环直接算即可,复杂度 $\Theta (n^2)$:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <iostream> #include <vector> #include <stdint.h> using Number = int64_t ;int main () { int n; std::vector<Number> a, b, c; std::ios::sync_with_stdio (false ); std::cin >> n; a.resize (n); b.resize (n); c.resize (n, 0 ); for (int i = 0 ; i < n; ++i) { std::cin >> a[i] >> b[i]; } for (int i = 0 ; i < n; ++i) { for (int j = i; j < n; ++j) { c[j - i] += a[j] * b[i]; } } for (int i = 0 ; i < n; ++i) { std::cout << c[i] << std::endl; } return 0 ; }
FFT Method 其实上述算法已经可以通过所有数据点了(可能是数据比较弱),不过显然不是课程期待的算法。
注意到$C[k]$的计算方式有点像卷积操作,令$a’[k]=a[n - 1 - k]$,则有$C[k]=\sum_{i=k}^{n-1}a[i]\times b[i-k]=\sum_{j=0}^{n-1-k}a’[(n-1-k)-j]\times b[j]$。至此已经有卷积的雏形了,再做一步代换$C’[k]=C[n-1-k]=\sum_{j=0}^{k}a’[k-j]\times b[j]$,终于出现了卷积形式。
有了卷积形式之后就可以使用 FFT 来加速了,复杂度 $\Theta (n \log n)$:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 #include <complex> #include <vector> #include <iostream> #include <math.h> struct FftContext { using Complex = std::complex<double >; FftContext (int len) : len_ (len) { if (len_ > 1 ) { int hflen = len_ / 2 ; const double PI = acos (-1 ); double ua = PI / hflen; w_.assign (hflen, Complex{}); for (int i = 0 ; i < w_.size (); ++i) { double arg = ua * i; w_[i] = Complex{cos (arg), sin (-arg)}; } } } ~FftContext () { } void Forward (std::vector<Complex> &data) const { if (len_ == 1 ) { return ; } BitRevese (data); for (int l = 2 ; l <= len_; l *= 2 ) { FftLayer (data, l, 0 ); } } void Backward (std::vector<Complex> &data) const { if (len_ == 1 ) { return ; } BitRevese (data); for (int l = 2 ; l <= len_; l *= 2 ) { FftLayer (data, l, 1 ); } for (int i = 0 ; i < len_; ++i) { data[i] /= len_; } } private : void BitRevese (std::vector<Complex> &data) const { int hflen = len_ / 2 ; for (int i = 1 , j = hflen; i < len_ - 1 ; ++i) { if (i < j) { std::swap (data[i], data[j]); } int k = hflen; while (j >= k) { j -= k; k /= 2 ; } if (j < k) { j += k; } } } void FftLayer (std::vector<Complex> &data, int gsize, int rev) const { int gcnt = len_ / gsize; int hfgs = gsize / 2 ; for (int i = 0 ; i < len_; i += gsize) { for (int j = 0 ; j < hfgs; ++j) { const Complex &wj = w_[j * gcnt]; Complex wmb = data[i + j + hfgs] * (rev ? std::conj (wj): wj); data[i + j + hfgs] = data[i + j] - wmb; data[i + j] += wmb; } } } int len_; std::vector<Complex> w_; }; uint32_t next_power_of_two (uint32_t x) { uint32_t r = 1 ; while (r < x) { r *= 2 ; } return r; } int main () { int n, pn; std::ios::sync_with_stdio (false ); std::cin >> n; pn = next_power_of_two (n); FftContext ctx{pn * 2 }; std::vector<std::complex<double >> a,b; a.assign (pn * 2 , std::complex<double >{}); b.assign (pn * 2 , std::complex<double >{}); for (int i = 0 ; i < n; ++i) { int ai, bi; std::cin >> ai >> bi; a[n - 1 - i] = ai; b[i] = bi; } ctx.Forward (a); ctx.Forward (b); for (int i = 0 ; i < a.size (); ++i) { a[i] *= b[i]; } ctx.Backward (a); for (int i = 0 ; i < n; ++i) { std::cout << (int )std::round (std::real (a[n - 1 - i])) << std::endl; } return 0 ; }