我是靠谱客的博主 积极蓝天,最近开发中收集的这篇文章主要介绍FFT学习笔记前言前置知识:复数FFTDFTIDFTNTT原根的性质分治FFT练习,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

文章目录

  • 前言
  • 前置知识:复数
  • FFT
      • 1.大名
      • 2.目的
      • 3.从O(n^2)到O(nlogn):
  • DFT
      • 0.目的:将系数表示转化为点值表示。
      • 1.求单位根
      • 2.求 A ( w n k ) A(w_n^k) A(wnk)
      • 3.蝴蝶操作
  • IDFT
      • 0.目的:将点值表示转回系数表示
      • 1.理解
    • 板子
    • “三次变两次”优化
  • NTT
  • 原根的性质
    • 板子
  • 分治FFT
  • 练习
      • bzoj 万径人踪灭
      • bzoj 二元运算
      • [TJOI 2017] DNA

前言

听了周指导的讲课,一脸懵逼,大概在这之前我连复数的概念都不是很了解。

然后本来准备看各种理解的,因为文化课的缘故,又一直没有整片的时间。

所以希望写一个博客记录一下我学FFT过程中没有理解的,或者自以为比较难理解的部分。

这里是几篇阅读了很有帮助的博客:

  1. zhouzhendong非常全,例题很多。
  2. miskcoo。
  3. GGN,很细致,还有对于二进制翻转的讲解,还有图非常优秀
  4. fhr两天没看忘光所有内容之后找到的一篇,看起来很棒棒。
  5. myy论文,留着看。。。

那就开始吧。


2019.7.31 再次忘光,准备重学。

发现之前学的时候大概还没学过高中数学,矩阵也不熟练,于是大概就是背了一个板子。


前置知识:复数

  1. 虚数单位 i 2 = − 1 i^2=-1 i2=1
  2. 复数表示 z = a + b ⋅ i ( a , b ∈ R ) z=a+bcdot i(a,bin R) z=a+bi(a,bR)
  3. 复平面,向量表示复数
  4. 复数加法:四边形法则
  5. 复数乘法:模长相乘,极角相加

FFT

1.大名

快速傅里叶变换

2.目的

求解多项式乘法。

即对于两个多项式 A ( x ) = a 0 + a 1 x + a 2 x 2 + ⋯ + a n x n A(x)=a_0+a_1x+a_2x^2+ cdots +a_{n}x^{n} A(x)=a0+a1x+a2x2++anxn B ( x ) = b 0 + b 1 x + b 2 x 2 + ⋯ + b m x m B(x)=b_0+b_1x+b_2x^2+ cdots +b_{m}x^{m} B(x)=b0+b1x+b2x2++bmxm C ( x ) = A ( x ) ∗ B ( x ) = a 0 b 0 + ( a 0 b 1 + a 1 b 0 ) x + ( a 0 b 2 + a 1 b 1 + a 2 b 0 ) x 2 + ⋯ C(x)=A(x)*B(x)=a_0b_0+(a_0b_1+a_1b0)x+(a_0b_2+a_1b_1+a_2b_0)x^2 + cdots C(x)=A(x)B(x)=a0b0+(a0b1+a1b0)x+(a0b2+a1b1+a2b0)x2+

3.从O(n^2)到O(nlogn):

对于上面两个式子,最暴力的想法是 O ( n 2 ) O(n^2) O(n2)将每两个项乘起来之后相加,即 C ( x ) = ∑ i = 0 n + m ∑ j = 0 i a j b i − j x i C(x)=sum_{i=0}^{n+m}sum_{j=0}^{i} a_jb_{i-j}x^i C(x)=i=0n+mj=0iajbijxi

FFT完全抛弃了上面这种暴力想法。FFT的做法是先 O ( n log ⁡ n ) O(n log n) O(nlogn)将系数表示转换成点值表示(具体定义见下方),再 O ( n ) O(n) O(n)将两个多项式的点值相乘,最后再 O ( n log ⁡ n ) O(nlog n) O(nlogn)将点值表达式转换回系数表达式。

A ( x ) = ∑ i = 0 n a i ∗ x i , B ( x ) = ∑ i = 0 n b i ∗ x i A(x)=sum_{i=0}^{n}a_i*x^i,B(x)=sum_{i=0}^{n}b_i*x^i A(x)=i=0naixi,B(x)=i=0nbixi

先变成 { x i , A ( x i ) ( 0 ≤ i ≤ n ) } , { x i , B ( x i ) ( 0 ≤ i ≤ n ) } { x_i,A(x_i) (0leq i leq n) },{ x_i,B(x_i) (0leq i leq n) } {xi,A(xi)(0in)},{xi,B(xi)(0in)}

相乘得 { x i , A ( x i ) ∗ B ( x i ) ( 0 ≤ i ≤ n ) } { x_i,A(x_i)*B(x_i) (0leq i leq n) } {xi,A(xi)B(xi)(0in)}

最后转回系数表达式。

至于如何做到 O ( n log ⁡ n ) O(nlog n) O(nlogn),看到下面就知道,我们取 x i = w n i x_i=w_n^i xi=wni,利用单位根优秀的性质就可以分治来做到了。

之所以写的这么烦,是因为我最开始连FFT可以干什么或者他的大体思路是什么都不知道,就被强行灌输了下面的一堆东西。并不能听懂,of course。

DFT

0.目的:将系数表示转化为点值表示。

系数表示: A ( x ) = ∑ i = 0 n − 1 a i ∗ x i A(x)=sum_{i=0}^{n-1}a_i*x^i A(x)=i=0n1aixi
点值表示: { x i , A ( x i ) ( 0 ≤ i < n ) } { x_i,A(x_i) (0leq i < n) } {xi,A(xi)(0i<n)}

1.求单位根

w n 0 , w n 1 , w n 2 , w n 3 , … , w n n − 1 w_n^0,w_n^1,w_n^2,w_n^3,dots,w_n^{n-1} wn0,wn1,wn2,wn3,,wnn1,其中 w n w_n wn表示辐角为正且最小的单位根,上标为次数
单位根在复平面上的几何意义:n等分单位圆,且有一根为1。

2.求 A ( w n k ) A(w_n^k) A(wnk)

再设两个多项式:
A 1 ( x ) = ∑ i = 0 n 2 − 1 a 2 ∗ i ∗ x i A 2 ( x ) = ∑ i = 0 n 2 − 1 a 2 ∗ i + 1 ∗ x i A1(x)=sum_{i=0}^{frac{n}{2}-1}a_{2*i}*x^i \ A2(x)=sum_{i=0}^{frac{n}{2}-1}a_{2*i+1}*x^i A1(x)=i=02n1a2ixiA2(x)=i=02n1a2i+1xi

所以
A ( x ) = A 1 ( x 2 ) + x ∗ A 2 ( x 2 ) A(x)=A1(x^2)+x*A2(x^2) A(x)=A1(x2)+xA2(x2)

那么现在代入 w n k ( k < n 2 ) w_n^k(k<frac{n}{2}) wnk(k<2n)
A ( w n k ) = A 1 ( w n 2 ∗ k ) + w n k ∗ A 2 ( w n 2 ∗ k ) = A 1 ( w n 2 k ) + w n k ∗ A 2 ( w n 2 k ) A(w_n^k)=A1(w_n^{2*k})+w_n^k*A2(w_n^{2*k}) \ quadquad=A1(w_{frac{n}{2}}^{k})+w_n^k*A2(w_{frac{n}{2}}^{k}) A(wnk)=A1(wn2k)+wnkA2(wn2k)=A1(w2nk)+wnkA2(w2nk)

联系到了 n 2 frac{n}{2} 2n,log快要出现了,那么代入 w n k + n 2 w_n^{k+frac{n}{2}} wnk+2n
A ( w n k + n 2 ) = A ( w n k ∗ w n n 2 )      = A ( − w n k ) = A 1 ( w n 2 k ) − w n k ∗ A 2 ( w n 2 k ) A(w_n^{k+frac{n}{2}})=A(w_n^k*w_{n}^{frac{n}{2}})qquad\ ; ;=A(-w_n^k)\ quadqquadqquadqquad=A1(w_{frac{n}{2}}^{k})-w_n^k*A2(w_{frac{n}{2}}^{k}) A(wnk+2n)=A(wnkwn2n)=A(wnk)=A1(w2nk)wnkA2(w2nk)

所以只需要交换一下系数,算出 A 1 ( w n 2 k ) A1(w_{frac{n}{2}}^{k}) A1(w2nk) A 2 ( w n 2 k ) ( k < n 2 ) A2(w_{frac{n}{2}}^{k})(k < frac{n}{2}) A2(w2nk)(k<2n),就可以 O ( n ) O(n) O(n)算出 A ( w n k ) A(w_{n}^{k}) A(wnk),于是总复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)

这里需要注释一下:
n = 2 k ( k ∈ N ) n=2^k(kin N) n=2k(kN),不够就高位补0
因为 w n 2 = w n 2 w_{frac{n}{2}}=w^2_{n} w2n=wn2,所以单位根只要一次预处理出来就好了

3.蝴蝶操作

上面的公式已经足够做递归版的DFT了,但是好像常数比较大,于是出现了迭代版。

看下面的矩阵(HE from zzd的博客)

∣ 000 001 010 011 100 101 110 111 0 1 2 3 4 5 6 7 0 2 4 6 ∣ 1 3 5 7 0 4 ∣ 2 6 ∣ 1 5 ∣ 3 7 0 ∣ 4 ∣ 2 ∣ 6 ∣ 1 ∣ 5 ∣ 3 ∣ 7 000 100 010 110 001 101 011 111 ∣ begin{vmatrix}000&&001&&010&&011&&100&&101&&110&&111\0&&1&&2&&3&&4&&5&&6&&7\0&&2&&4&&6&|&1&&3&&5&&7\0&&4&|&2&&6&|&1&&5&|&3&&7\0&|&4&|&2&|&6&|&1&|&5&|&3&|&7\000&&100&&010&&110&&001&&101&&011&&111end{vmatrix} 00000000000011244100010242201001136661101004111001101535510111065330111117777111

发现在递归的最后一层下标恰好是原下标的二进制翻转。

观察蝴蝶操作:第一次翻转,将末位为0的都放在了左边,末位为1的都放在了右边,然后分开,左边和右边就不会再交换了。然后递归左右两边,做同样的操作,发现最后一位都一样,倒数第二位是0101…,所以交换系数的操作又相当于再把倒数第二位为0的放左边,为1的放右边…这就是一个类似排序的东西嘛,把二进制翻转之后的数排成了正序。

知道了这个性质有什么用吗?因为递归最底层的 A ( x ) A(x) A(x)度为1,所以 A ( x ) = A ( ω 1 k ) = A ( 1 ) = a i A(x)=A(omega_{1}^{k})=A(1)=a_i A(x)=A(ω1k)=A(1)=ai,然后再一层一层推到顶层,就可以得到所有 A ( ω n k ) A(omega_{n}^{k}) A(ωnk)了。

IDFT

这一段的矩阵我实在搞不出来,所以借鉴抄了zzd的博客,请看博客顶部链接去%zzd。

0.目的:将点值表示转回系数表示

1.理解

拿矩阵来理解DFT的话,就是这样的:

D = [ ( ω n 0 ) 0 ( ω n 0 ) 1 ⋯ ( ω n 0 ) n − 1 ( ω n 1 ) 0 ( ω n 1 ) 1 ⋯ ( ω n 1 ) n − 1 ⋮ ⋮ ⋱ ⋮ ( ω n n − 1 ) 0 ( ω n n − 1 ) 1 ⋯ ( ω n n − 1 ) n − 1 ] mathbf D = begin{bmatrix} (omega_n^{0})^0 & (omega_n^{0})^1 & cdots & (omega_n^{0})^{n-1} \ (omega_n^{1})^0 & (omega_n^{1})^1 & cdots & (omega_n^{1})^{n-1} \ vdots & vdots & ddots & vdots \ (omega_n^{n-1})^0 & (omega_n^{n-1})^1 & cdots & (omega_n^{n-1})^{n-1} end{bmatrix} D=(ωn0)0(ωn1)0(ωnn1)0(ωn0)1(ωn1)1(ωnn1)1(ωn0)n1(ωn1)n1(ωnn1)n1

那么 D ⋅ [ a 1 a 2 ⋮ a n ] = [ A ( x 1 ) A ( x 2 ) ⋮ A ( x n ) ] mathbf D cdot begin{bmatrix}a_1\ a_2 \ vdots \ a_n end{bmatrix}=begin{bmatrix}A(x_1)\ A(x_2) \ vdots \ A(x_n) end{bmatrix} Da1a2an=A(x1)A(x2)(xn)

所以做IDFT,相当于两边同乘 D − 1 mathbf D^{-1} D1,变成酱紫:

[ a 1 a 2 ⋮ a n ] = D − 1 ⋅ [ A ( x 1 ) A ( x 2 ) ⋮ A ( x n ) ] begin{bmatrix}a_1\ a_2 \ vdots \ a_n end{bmatrix}=mathbf D^{-1} cdot begin{bmatrix}A(x_1)\ A(x_2) \ vdots \ A(x_n) end{bmatrix} a1a2an=D1A(x1)A(x2)A(xn)

因为我们知道了点值表示,所以再当做DFT再做一遍就好了。

所以对这个 D mathbf D D求逆,可以先手模几个小的然后找找规律,发现逆矩阵长这个样子:

D − 1 = [ ( ω n − 0 ) 0 n ( ω n − 0 ) 1 n ⋯ ( ω n − 0 ) n − 1 n ( ω n − 1 ) 0 n ( ω n − 1 ) 1 n ⋯ ( ω n − 1 ) n − 1 n ⋮ ⋮ ⋱ ⋮ ( ω n − ( n − 1 ) ) 0 n ( ω n − ( n − 1 ) ) 1 n ⋯ ( ω n − ( n − 1 ) ) n − 1 n ] mathbf D^{-1} = begin{bmatrix} frac{(omega_n^{-0})^0}{n} & frac{(omega_n^{-0})^1}{n} & cdots & frac{(omega_n^{-0})^{n-1}}{n} \ frac{(omega_n^{-1})^0}{n} & frac{(omega_n^{-1})^1}{n} & cdots & frac{(omega_n^{-1})^{n-1}}{n} \ vdots & vdots & ddots & vdots \ frac{(omega_n^{-(n-1)})^0}{n} & frac{(omega_n^{-(n-1)})^1}{n} & cdots & frac{(omega_n^{-(n-1)})^{n-1}}{n} end{bmatrix} D1=n(ωn0)0n(ωn1)0n(ωn(n1))0n(ωn0)1n(ωn1)1n(ωn(n1))1n(ωn0)n1n(ωn1)n1n(ωn(n1))n1

可以发现

( D ⋅ D − 1 ) i , j = ∑ k = 0 n − 1 ω k ( i − j ) n (mathbf Dcdot mathbf D^{-1})_{i,j}=frac{sum_{k=0}^{n-1}omega^{k(i-j)}}{n} (DD1)i,j=nk=0n1ωk(ij)

i = j i=j i=j 时为 1 1 1 ,其他时候为 0 0 0 ,就是单位矩阵。所以两个矩阵互逆。

简单说就是对于矩阵中每一个复数取实部不变虚部取反,然后除以 n n n


所以假如看到这里,那你应该已经会了基础的FFT了,所以快去写板子吧。

板子

[ZJOI2014]力

#include<bits/stdc++.h>
using namespace std;
const int N = 1<<18;
const double pi = acos(-1);
int m, n, l, rev[N];
struct C{
    double r, i;
    C(){r = i = 0;}
    C(double x, double y){r = x; i = y;}
    C operator + (C u){return C(r+u.r, i+u.i);}
    C operator - (C u){return C(r-u.r, i-u.i);}
    C operator * (C u){return C(r*u.r-i*u.i, r*u.i+i*u.r);}
}q[N], p[N], a[N], w[N];

void fft(C *a)
{
    for (int i = 0; i < n; ++ i)
        if (rev[i] < i)
            swap(a[i], a[rev[i]]);
    for (int i = 1, t = n>>1; i < n; i <<= 1, t >>= 1)
        for (int j = 0; j < n; j += (i<<1))
            for (int k = 0; k < i; ++ k){
                C x = a[j+k];
                C y = w[t*k]*a[i+j+k];
                a[j+k] = x+y;
                a[i+j+k] = x-y;
            }
}

int main()
{
    scanf("%d", &m);
    for (int i = 0; i < m; ++ i){
        scanf("%lf", &q[i].r);
        p[m-i-1].r = q[i].r;
    }
    for (int i = 1; i < m; ++ i)
        a[i].r = 1.0/i/i;
    for (n = 1, l = 0; n < (m<<1); n <<= 1, ++ l);
    for (int i = 0; i < n; ++ i){
        rev[i] = (rev[i>>1]>>1)^((i&1)<<(l-1));
        w[i] = C(cos(2.0*pi*i/n), sin(2.0*pi*i/n));
    }
    fft(q); fft(p); fft(a);
    for (int i = 0; i < n; ++ i){
        q[i] = q[i]*a[i], p[i] = p[i]*a[i];
        w[i].i *= -1; // !!!
    }
    fft(q); fft(p);
    for (int i = 0; i < n; ++ i)
        p[i].r = p[i].r/n, q[i].r = q[i].r/n;
    for (int i = 0; i < m; ++ i)
        printf("%.10fn", q[i].r-p[m-i-1].r);
    return 0;
}

“三次变两次”优化

假设我们要求的是 ( A ∗ B ) ( x ) (A*B)(x) (AB)(x) ,且两个多项式的系数都是实数。

C ( x ) = ( A ( x ) + i ⋅ B ( x ) ) 2 = A ( x ) 2 − B ( x ) 2 + 2 ⋅ i ⋅ ( A ∗ B ) ( x ) C(x)=(A(x)+icdot B(x))^2=A(x)^2-B(x)^2+2cdot icdot(A*B)(x) C(x)=(A(x)+iB(x))2=A(x)2B(x)2+2i(AB)(x) ,其中 i i i 是虚数单位。

那么我们只要两次 FFT 求出 C ( x ) 2 C(x)^2 C(x)2 ,这个多项式的虚部就是答案。

小小的大大地优化常数。

NTT

数论变换,和FFT类似,只是多了一个取模的操作。既然有取模那我们就不能用复数了,那不是不能用单位根的优秀性质了吗?

所以我们找到了原根来代替单位根。在模意义下原根有单位根所有需要用到的性质。

原根的性质

g g g是质数 p p p的原根,再设 ω n = g ( p − 1 ) / n omega_n=g^{(p-1)/n} ωn=g(p1)/n p p p满足 n ∣ p − 1 n|p-1 np1)。

  1. ω n n ≡ 1 ( m o d    p ) omega_n^{n}equiv 1(mod; p) ωnn1(modp)
  2. ω n 0 , ω n 1 , … , ω n n − 1 omega_n^0,omega_n^{1},dots,omega_n^{n-1} ωn0,ωn1,,ωnn1 m o d    p mod; p modp意义下互不相同(重要!)
  3. ω n k ≡ − ω n k + n / 2 omega_n^kequiv -omega_n^{k+n/2} ωnkωnk+n/2 ω n n / 2 ≡ − 1 ( m o d    p ) omega_n^{n/2}equiv-1(mod; p) ωnn/21(modp)

所以原根可以完全替代单位根完成NTT。

板子

51nod大数乘法

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1<<20;
const int mod = 998244353;
char sa[N], sb[N];
int na, nb, a[N], b[N], n, l, r[N], g[N];

int add(int x, int y){int z = (x+y)%mod; if (z < 0) z += mod; return z;}
int mul(int x, int y){int z = 1ll*x*y%mod; if (z < 0) z += mod; return z;}

int Pow(int x, int y)
{
	int ret = 1;
	while (y){if (y&1) ret = mul(ret, x); x = mul(x, x); y >>= 1;}
	return ret;
}

void Init()
{
	scanf("%s%s", sa, sb);
	na = strlen(sa); nb = strlen(sb);
	for (int i = 0; i < na; ++ i) a[na-i-1] = sa[i]-'0'; // reverse !!!
	for (int i = 0; i < nb; ++ i) b[nb-i-1] = sb[i]-'0';
}

void ntt(int *a)
{
	for (int i = 0; i < n; ++ i) if (r[i] < i) swap(a[i], a[r[i]]);
	for (int i = 1, t = n>>1; i < n; i <<= 1, t >>= 1)
		for (int j = 0; j < n; j += (i<<1))
			for (int k = 0; k < i; ++ k){
				int x = a[j+k], y = mul(g[t*k], a[i+j+k]);
				a[j+k] = add(x, y); a[i+j+k] = add(x, -y);
			}
}

int main()
{
	Init();
	for (n = 1, l = 0; n < na+nb; n <<= 1, ++ l);
	g[0] = 1; g[1] = Pow(3, (mod-1)/n); // mod 998244353 g=3, g^(p-1)=wn^n=1, wn=g^((p-1)/n)
	for (int i = 2; i < n; ++ i) g[i] = mul(g[i-1], g[1]);
	for (int i = 0; i < n; ++ i) r[i] = (r[i>>1]>>1)^((i&1)<<(l-1));
	ntt(a); ntt(b);
	for (int i = 0; i < n; ++ i) a[i] = mul(a[i], b[i]);
	g[0] = 1; g[1] = Pow(g[1], mod-2);
	for (int i = 2; i < n; ++ i) g[i] = mul(g[i-1], g[1]);
	ntt(a);
	int invn = Pow(n, mod-2);
	for (int i = 0; i < n; ++ i) a[i] = mul(a[i], invn);
	for (int i = 0; i < n; ++ i){a[i+1] += a[i]/10; a[i] %= 10;}
	for (-- n; n && !a[n]; -- n);
	for (int i = n; i >= 0; -- i)
		printf("%d", a[i]);
	return 0;
}

分治FFT

CDQ分治+FFT

求解 f n = ∑ i = 1 n a i f n − i f_n=sum_{i=1}^{n}a_if_{n-i} fn=i=1naifni

每次做完左边,然后用左边贡献右边,再做右边 (废话)

练习

bzoj 万径人踪灭

bzoj3160

题意

给一个字符串,求不连续的回文子序列(不连续意味着不能是子串)。

思路

首先一个无比显然的容斥:不连续回文子序列=所有回文子序列-回文子串

于是把原问题拆成了两个毫不相干的问题。

第二个问题用manacher做,就是模板。

第一个问题,用一般fft处理字符串问题的套路,找卷积的形式。假如我们设 f ( n ) = ∑ i = 0 n ( s ( i ) − s ( n − i ) ) 2 f(n)=sum_{i=0}^{n}(s(i)-s(n-i))^2 f(n)=i=0n(s(i)s(ni))2

这个 f ( n ) f(n) f(n)的意义就是以 n 2 frac{n}{2} 2n为对称中心(假如n是奇数那就是两个字符中间那个位置)的所有有序字符对中有多少个不相同的。所以只要求出 f ( n ) f(n) f(n),用总对数减一下就是相同的。

然后我们来拆出卷积:

f ( n ) = ∑ i = 0 n s ( i ) 2 + s ( n − i ) 2 − 2 s ( i ) s ( n − i ) = 2 ∑ i = 0 n s ( i ) 2 − 2 ∑ i = 0 n s ( i ) s ( n − i ) f(n)=sum_{i=0}^{n}s(i)^2+s(n-i)^2-2s(i)s(n-i)\=2sum_{i=0}^{n}s(i)^2-2sum_{i=0}^{n}s(i)s(n-i) f(n)=i=0ns(i)2+s(ni)22s(i)s(ni)=2i=0ns(i)22i=0ns(i)s(ni)

一个前缀和一个卷积,搞定。

注意

  1. f ( n ) f(n) f(n)中的 n n n大于字符串长度时,上面式子中的 i i i不一定能取到边界,需要再判一下。

  2. 卡精度,FFT太毒瘤了。

bzoj 二元运算

题意:

有一个运算

x    o p t    y = { x + y , x < y x − y , x ≥ y x;opt;y=left{ begin{aligned} & x+y &,x<y \ & x-y &,xge y end{aligned} right. xopty={x+yxy,x<y,xy

给出序列 { a i } {a_i} {ai} { b i } {b_i} {bi} ,每次询问一个数 c c c ,问有多少组 i , j i,j i,j 满足 a i    o p t    b j = c a_i;opt;b_j=c aioptbj=c

思路:

假如单独对 x + y x+y x+y 或者 x − y x-y xy 做的话,是一个 FFT 的套路。

a i a_i ai b i b_i bi 放进桶里,做一遍 FFT ,得到的多项式的系数 c i c_i ci 表示的就是两边各选一个数加起来为 i i i 的方案数。

但是这题对 x , y x,y x,y 的大小关系有限制。所以可以考虑分治来去掉限制,每次分一个 m i d mid mid ,考虑 m i d ∈ [ a i , b j ] midin[a_i,b_j] mid[ai,bj] 或者 m i d ∈ [ b j , a i ] midin[b_j,a_i] mid[bj,ai] 的数对的贡献。因为大小关系确定,所以直接 FFT 就好了。

最后 O ( n log ⁡ 2 n ) O(nlog^2n) O(nlog2n) 。因为 FFT 常数有点大,所以稍稍卡一下常,比如要做 FFT 的两个数组中只要有一个是全空的就不做。

[TJOI 2017] DNA

题意:

两个串 S 1 , S 2 S1,S2 S1,S2 ,字符集 { ′ A ′ , ′ G ′ , ′ C ′ , ′ T ′ } {'A','G','C','T'} {A,G,C,T}

S 1 S1 S1 中有多少子串可以通过修改小于等于 3 个字符变成 S 2 S2 S2

思路:

题目要我们求 S 1 S1 S1 中有多少子串满足,与 S 2 S2 S2 的对应位置字符不同的位置 ≤ 3 le 3 3

按照套路,将某一个串翻转,分字符讨论。

对于每一个字符 c c c,把字符串等于 c c c 的位置赋值为 1,其余位置赋值为 0 ,那么某个位置的匹配中, S 1 S1 S1 S 2 S2 S2 某位都为 c c c 的位置个数 e q u n = ∑ i + j = n a i b j equ_n=sum^{i+j=n}a_ib_j equn=i+j=naibj ,是卷积的形式。

在 bzoj 上会被卡常。并且貌似 NTT 还没 三次变两次优化的 FFT 快。所以还是乖乖用 SAM 做吧。

最后

以上就是积极蓝天为你收集整理的FFT学习笔记前言前置知识:复数FFTDFTIDFTNTT原根的性质分治FFT练习的全部内容,希望文章能够帮你解决FFT学习笔记前言前置知识:复数FFTDFTIDFTNTT原根的性质分治FFT练习所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(56)

评论列表共有 0 条评论

立即
投稿
返回
顶部