参考来源:
十分简明易懂的FFT(快速傅里叶变换)
小学生都能看懂的FFT!!!
FFT是什么
快速傅里叶变换(FFT)是一种能在 O ( n log n ) O(n\log{n}) O(nlogn) 的时间内将一个多项式转换成它的点值表示的算法。
FFT的作用
快速计算多项式乘法(即卷积)
(还可以用到字符串的模糊匹配)
前置知识
系数表示的多项式乘法【 O ( n 2 ) O(n2) O(n2)】:枚举A(x)中的每一项,分别与B(x)中的每一项相乘,求得新的多项式C(x)。
点值表示的多项式乘法【 O ( n ) O(n) O(n)】: C ( x i ) = A ( x i ) × B ( x i ) C(xi)=A(xi)×B(xi) C(xi)=A(xi)×B(xi)。
那么我们要计算多项式的乘法,就可以先将系数表示转换为点值表示,相乘以后,再将点值表示转换为系数表示即可。——这个转换过程就用到了FFT。
朴素:系数转点值的算法叫DFT(离散傅里叶变换)
朴素:点值转系数叫IDFT(离散傅里叶逆变换)
如果将单位圆等分为8等分,则橙色点即为n=8时要取的点,从(1,0)点开始,逆时针从0号开始标号,标到7号。
(这个图是我偷的 )
记编号为k的点代表的复数值为 ω n k ω^k_n ωnk,那么由模长相乘,极角相加可知 ( ω n 1 ) k = ω n k (ω^1_n)^k=ω^k_n (ωn1)k=ωnk
其中 ω n 1 ω^1_n ωn1称为n次单位根,而且每一个ω都可以求出为
ω n k = cos k n 2 π + i sin k n 2 π ω^k_n=\cos{\frac{k}{n}2π}+i\sin{\frac{k}{n}2π} ωnk=cosnk2π+isinnk2π
那么 ω n 0 , ω n 1 , . . . , ω n n − 1 ω^0_n,ω^1_n,...,ω^{n−1}_n ωn0,ωn1,...,ωnn−1即为我们要代入的 x 0 , x 1 , . . . , x n − 1 x^0,x^1,...,x^{n−1} x0,x1,...,xn−1。
单位根的性质
1、 ω n k = ω 2 n 2 k ω_n^k=ω_{2n}^{2k} ωnk=ω2n2k
它们表示的点(或向量)表示的复数是相同的。
2、 ω n k + n 2 = − ω n k ω_n^{k+\frac{n}{2}}=−ω_n^k ωnk+2n=−ωnk
它们表示的点关于原点对称,所表示的复数实部相反,所表示的向量等大反向。
3、 ω n 0 = ω n n ω_n^0=ω_n^n ωn0=ωnn
证明:把系数带到三角函数表达式中即可证明。
DFT 分治 --> FFT
那么如果可以求出 A 1 ( ω n k ) A1(ω_n^k) A1(ωnk)和 A 2 ( ω n k ) A2(ω^k_n) A2(ωnk)的值,我们就可以求出 A ( ω n k ) A(ω_n^k) A(ωnk)和 A ( ω n k + n 2 ) A(ω^{k + \frac{n}{2}}_n) A(ωnk+2n)的值( k < n 2 k< \frac{n}{2} k<2n),也就是知道 A ( ω n i ) A(ω_n^i) A(ωni)( 0 < = i < n 0 <= i< n 0<=i<n)的值了。
分治边界是n=1,直接return。
分治的复杂度:
T ( n ) = 2 T ( n 2 ) + O ( n ) = O ( n log n ) T(n) = 2T(\frac{n}{2}) + O(n)= O(n\log{n}) T(n)=2T(2n)+O(n)=O(nlogn)
以下板子都是我抄的,没有测试过,看个思路就可以了。
1、递归版
#include
#define cp complex
cp omega(int n, int k)
{
return cp(cos(2 * PI * k / n), sin(2 * PI * k / n));
}
void fft(cp *a, int n, bool inv)
{
if(n == 1)
return;
static cp buf[N];
int m = n / 2;//mid
for(int i = 0; i < m; i++) //将每一项按照奇偶分为两组
{
buf[i] = a[2 * i];
buf[i + m] = a[2 * i + 1];
}
for(int i = 0; i < n; i++)
a[i] = buf[i];
fft(a, m, inv); //递归处理两个子问题
fft(a + m, m, inv);
for(int i = 0; i < m; i++) //枚举x,计算A(x)
{
cp x = omega(n, i);
if(inv)
x = conj(x);
//conj是一个自带的求共轭复数的函数,精度较高。当复数模为1时,共轭复数等于倒数
buf[i] = a[i] + x * a[i + m]; //根据之前推出的结论计算
buf[i + m] = a[i] - x * a[i + m];
}
for(int i = 0; i < n; i++)
{
a[i] = buf[i];
if(inv)
a[i] = (int)(a[i]/n + 0.5);//注意精度
}
}
//求a,b相乘
cp a[MAXN],b[MAXN];
int c[MAXN];
fft(a, n, 0), fft(b, n, 0);//0系数转点值
for(int i = 0; i < n; i++)
a[i] *= b[i];
fft(a, n, 1);//1点值转系数
for(int i = 0; i < n; i++)
c[i] = a[i];
思路来源:补充——FFT中的二进制翻转问题
int bit = 0;
while((1 << bit) < n)
bit++;
for(int i = 0; i < n; i++)
{
rev[i] = (rev[i>>1]>>1) | ((i&1)<<(bit-1));
if(i < rev[i])
swap(a[i], a[ rev[i] ]); // i < t 的限制使得每对点只被交换一次(否则交换两次相当于没交换)
}
cp a[N], b[N], omg[N], inv[N];
void init()
{
for(int i = 0; i < n; i++)
{
omg[i] = cp(cos(2 * PI * i / n), sin(2 * PI * i / n));
inv[i] = conj(omg[i]);
}
}
void fft(cp *a, cp *omg)
{
int lim = 0;
while((1 << lim) < n)
lim++;
for(int i = 0; i < n; i++)
{
int t = 0;
for(int j = 0; j < lim; j++)
if((i >> j) & 1)
t |= (1 << (lim - j - 1));
if(i < t)
swap(a[i], a[t]); // i < t 的限制使得每对点只被交换一次(否则交换两次相当于没交换)
}
static cp buf[N];
for(int l = 2; l <= n; l *= 2)
{
int m = l / 2;
for(int j = 0; j < n; j += l)
for(int i = 0; i < m; i++)
{
buf[j + i] = a[j + i] + omg[n / l * i] * a[j + i + m];
buf[j + i + m] = a[j + i] - omg[n / l * i] * a[j + i + m];
}
for(int j = 0; j < n; j++)
a[j] = buf[j];
}
}
3、蝴蝶操作
“蝴蝶操作”的目的是:丢掉buf数组。
buf的使用:
a [ j + i ] = a [ j + i ] + o m g [ n / l ∗ i ] ∗ a [ j + i + m ] a[j + i] = a[j + i] + omg[n / l * i] * a[j + i + m] a[j+i]=a[j+i]+omg[n/l∗i]∗a[j+i+m]
a [ j + i + m ] = a [ j + i ] − o m g [ n / l ∗ i ] ∗ a [ j + i + m ] a[j + i + m] = a[j + i] - omg[n / l * i] * a[j + i + m] a[j+i+m]=a[j+i]−omg[n/l∗i]∗a[j+i+m]
要求这两行不能互相影响,所以我们需要buf数组。
原地进行:
c p t = o m g [ n / l ∗ i ] ∗ a [ j + i + m ] cp t = omg[n / l * i] * a[j + i + m] cpt=omg[n/l∗i]∗a[j+i+m]
a [ j + i + m ] = a [ j + i ] − t a[j + i + m] = a[j + i] - t a[j+i+m]=a[j+i]−t
a [ j + i ] = a [ j + i ] + t a[j + i] = a[j + i] + t a[j+i]=a[j+i]+t
cp a[N], b[N], omg[N], inv[N];
void init(){
for(int i = 0; i < n; i++){
omg[i] = cp(cos(2 * PI * i / n), sin(2 * PI * i / n));
inv[i] = conj(omg[i]);
}
}
void fft(cp *a, cp *omg){
int lim = 0;
while((1 << lim) < n) lim++;
for(int i = 0; i < n; i++){
int t = 0;
for(int j = 0; j < lim; j++)
if((i >> j) & 1) t |= (1 << (lim - j - 1));
if(i < t) swap(a[i], a[t]); // i < t 的限制使得每对点只被交换一次(否则交换两次相当于没交换)
}
for(int l = 2; l <= n; l *= 2){
int m = l / 2;
for(cp *p = a; p != a + n; p += l)
for(int i = 0; i < m; i++){
cp t = omg[n / l * i] * p[i + m];
p[i + m] = p[i] - t;
p[i] += t;
}
}
}
高精度使用
#include
#include
#include
#include
#include
#define space putchar(' ')
#define enter putchar('\n')
using namespace std;
typedef long long ll;
template <class T>
void read(T &x)
{
char c;
bool op = 0;
while(c = getchar(), c < '0' || c > '9')
if(c == '-')
op = 1;
x = c - '0';
while(c = getchar(), c >= '0' && c <= '9')
x = x * 10 + c - '0';
if(op)
x = -x;
}
template <class T>
void write(T x)
{
if(x < 0)
putchar('-'), x = -x;
if(x >= 10)
write(x / 10);
putchar('0' + x % 10);
}
const int N = 1000005;
const double PI = acos(-1);
typedef complex <double> cp;
char sa[N], sb[N];
int n = 1, lena, lenb, res[N];
cp a[N], b[N], omg[N], inv[N];
void init()
{
for(int i = 0; i < n; i++)
{
omg[i] = cp(cos(2 * PI * i / n), sin(2 * PI * i / n));
inv[i] = conj(omg[i]);
}
}
void fft(cp *a, cp *omg)
{
int lim = 0;
while((1 << lim) < n)
lim++;
for(int i = 0; i < n; i++)
{
int t = 0;
for(int j = 0; j < lim; j++)
if((i >> j) & 1)
t |= (1 << (lim - j - 1));
if(i < t)
swap(a[i], a[t]); // i < t 的限制使得每对点只被交换一次(否则交换两次相当于没交换)
}
for(int l = 2; l <= n; l *= 2)
{
int m = l / 2;
for(cp *p = a; p != a + n; p += l)
for(int i = 0; i < m; i++)
{
cp t = omg[n / l * i] * p[i + m];
p[i + m] = p[i] - t;
p[i] += t;
}
}
}
int main()
{
scanf("%s%s", sa, sb);
lena = strlen(sa), lenb = strlen(sb);
while(n < lena + lenb)
n *= 2;
for(int i = 0; i < lena; i++)
a[i].real(sa[lena - 1 - i] - '0');
for(int i = 0; i < lenb; i++)
b[i].real(sb[lenb - 1 - i] - '0');
init();
fft(a, omg);
fft(b, omg);
for(int i = 0; i < n; i++)
a[i] *= b[i];
fft(a, inv);
for(int i = 0; i < n; i++)
{
res[i] += floor(a[i].real() / n + 0.5);
res[i + 1] += res[i] / 10;
res[i] %= 10;
}
for(int i = res[lena + lenb - 1] ? lena + lenb - 1: lena + lenb - 2; i >= 0; i--)
putchar('0' + res[i]);
enter;
return 0;
}
例题:A * B Problem Plus HDU - 1402 (大数高精度乘法)
Calculate A * B.
Input
Each line will contain two integers A and B. Process to end of file.
Note: the length of each integer will not exceed 50000.
Output
For each case, output A * B in one line.
Sample Input
1
2
1000
2
Sample Output
2
2000
AC代码:
#include
#include
#include
#include
#include
#include
#include
#define maxn (1<<16)
#define pi acos(-1)
using namespace std;
struct Complex
{
double re, im;
Complex(double r = 0.0, double i = 0.0)
{
re = r, im = i;
}
void print()
{
printf("%lf %lf\n", re, im);
}
};
Complex operator +(const Complex&A, const Complex&B)
{
return Complex(A.re + B.re, A.im + B.im);
}
Complex operator -(const Complex&A, const Complex&B)
{
return Complex(A.re - B.re, A.im - B.im);
}
Complex operator *(const Complex&A, const Complex&B)
{
return Complex(A.re * B.re - A.im * B.im, A.re * B.im + A.im * B.re);
}
//以每一位为系数, 那么多项式长度不超过50000
//对应的乘积的长度不会超过100000, 也就是不超过(1 << 17) = 131072
Complex a[maxn * 2], b[maxn * 2], inv[2][maxn * 2];
int N, na, nb, rev[maxn * 2], ans[maxn * 2];
string s1, s2;
void FFT(Complex *a, int f)//f表示DFT(0)还是IDFT(1)
{
Complex x, y;
for(int i = 0; i < N; i++)
if(i < rev[i]) //不加这条if会交换两次(就是没交换)
swap(a[i], a[rev[i]]);
for(int i = 1; i < N; i <<= 1) //i是准备合并序列的长度的二分之一
for(int j = 0, t = N / (i << 1); j < N; j += i << 1) //i*2是准备合并序列的长度,j是合并到了哪一位(第某段的开头的坐标),t表示每一份单位根占单位圆的多少
for(int k = 0, l = 0; k < i; k++, l += t) //k是第某段内的第i位(只扫描前一半,后面一半可以同时求)
{
x = inv[f][l] * a[j + k + i]; //inv[f][l]表示第L份单位根
y = a[j + k];
a[j + k] = y + x;
a[j + k + i] = y - x;
}
if(f)
for(int i = 0; i < N; i++)
a[i].re /= N;
}
void Init()
{
int bit = 0;
while((1 << bit) < N)
bit++;
for(int i = 0; i < N; i++)//预处理逆反位置
{
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bit - 1));
}
for(int i = 0; i < N; i++)//预处理单位根
inv[0][i] = inv[1][i] = Complex(cos(2 * pi * i / N), sin(2 * pi * i / N)), inv[1][i].im = -inv[0][i].im;
}
void pre()
{
na = s1.length();
nb = s2.length();
for(N = 1; N < na || N < nb; N <<= 1);
N <<= 1;
for(int i = 0; i < N; i++)//多组输入记得清空数组
{
if(i < na)
a[i].re = s1[na - 1 - i] - '0', a[i].im = 0;
else
a[i].re = a[i].im = 0;
if(i < nb)
b[i].re = s2[nb - 1 - i] - '0', b[i].im = 0;
else
b[i].re = b[i].im = 0;
ans[i] = 0;
}
}
void work()
{
Init();
FFT(a, 0), FFT(b, 0);
for(int i = 0; i < N; i++)
a[i] = a[i] * b[i];
FFT(a, 1);
for(int i = 0; i < N; i++)//进位处理
{
ans[i] += (int)(a[i].re + 0.5);
ans[i + 1] += ans[i] / 10;
ans[i] %= 10;
}
int flag = 0;
for(int i = N - 1; i >= 0; i--)//输出处理
{
if(ans[i])
{
printf("%d", ans[i]);
flag = 1;
}
else if(flag || i == 0)//如果不是前导0 或者 (全部都是0,那么flag = 0,到最后一位时,0不能忽略,必须输出一个0)
printf("0");
}
printf("\n");
}
int main()
{
while(cin >> s1 >> s2)
{
pre();
work();
}
return 0;
}
题意:模板串P和文本串T都带有?号,可以匹配任意一个字符,求P在T中所有的出现位置。
定义:文本偏移量 f ( A , B ) = s i g m a ( ∣ A [ i ] − B [ i ] ∣ ) f(A,B) = sigma(|A[i] - B[i]|) f(A,B)=sigma(∣A[i]−B[i]∣)
由于绝对值不好求,故转化为 平方
f ( A , B ) = s i g m a ( ( ∣ A [ i ] − B [ i ] ∣ ) 2 ) = s i g m a A [ i ] 2 + s i g m a B [ i ] 2 − 2 ∗ s i g m a A [ i ] ∗ B [ i ] f(A,B) = sigma((|A[i] - B[i]|)^2)= sigmaA[i] ^2+sigmaB[i] ^2-2*sigmaA[i] *B[i] f(A,B)=sigma((∣A[i]−B[i]∣)2)=sigmaA[i]2+sigmaB[i]2−2∗sigmaA[i]∗B[i]
由于在多项式乘法中, C k ( 满 足 i + j = k ) C_k(满足i+j=k) Ck(满足i+j=k)
若有通配符?,我们只需要改变一下函数 f ( A , B ) f(A,B) f(A,B)即可。
若要计算 A字符串(N)、B字符串(M),在B中有多少个 B [ i , i + N ) B [i,i+N) B[i,i+N)区间,能够满足有 [ N ∗ 0.75 ] [N*0.75] [N∗0.75]的字符与A匹配(不要求连续),即有75%的字符与B子区间对应相同。
a
~b
a
]即表示字符a
一致的位置有多少个。