概述
文章目录
- 前言
- 前置知识:复数
- 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过程中没有理解的,或者自以为比较难理解的部分。
这里是几篇阅读了很有帮助的博客:
- zhouzhendong非常全,例题很多。
- miskcoo。
- GGN,很细致,还有对于二进制翻转的讲解,
还有图非常优秀。 - fhr两天没看忘光所有内容之后找到的一篇,看起来很棒棒。
- myy论文,留着看。。。
那就开始吧。
2019.7.31 再次忘光,准备重学。
发现之前学的时候大概还没学过高中数学,矩阵也不熟练,于是大概就是背了一个板子。
前置知识:复数
- 虚数单位 i 2 = − 1 i^2=-1 i2=−1
- 复数表示 z = a + b ⋅ i ( a , b ∈ R ) z=a+bcdot i(a,bin R) z=a+b⋅i(a,b∈R)
- 复平面,向量表示复数
- 复数加法:四边形法则
- 复数乘法:模长相乘,极角相加
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=0∑n+mj=0∑iajbi−jxi
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=0∑nai∗xi,B(x)=i=0∑nbi∗xi
先变成 { 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)(0≤i≤n)},{xi,B(xi)(0≤i≤n)}
相乘得 { 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)(0≤i≤n)}
最后转回系数表达式。
至于如何做到 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=0n−1ai∗xi
点值表示:
{
x
i
,
A
(
x
i
)
(
0
≤
i
<
n
)
}
{ x_i,A(x_i) (0leq i < n) }
{xi,A(xi)(0≤i<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,…,wnn−1,其中
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=0∑2n−1a2∗i∗xiA2(x)=i=0∑2n−1a2∗i+1∗xi
所以
A
(
x
)
=
A
1
(
x
2
)
+
x
∗
A
2
(
x
2
)
A(x)=A1(x^2)+x*A2(x^2)
A(x)=A1(x2)+x∗A2(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(wn2∗k)+wnk∗A2(wn2∗k)=A1(w2nk)+wnk∗A2(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(wnk∗wn2n)=A(−wnk)=A1(w2nk)−wnk∗A2(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(k∈N),不够就高位补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} ∣∣∣∣∣∣∣∣∣∣∣∣0000000000∣0011244100∣∣0102422010∣0113666110∣∣∣1004111001∣1015355101∣∣1106533011∣1117777111∣∣∣∣∣∣∣∣∣∣∣∣
发现在递归的最后一层下标恰好是原下标的二进制翻转。
观察蝴蝶操作:第一次翻转,将末位为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⋮(ωnn−1)0(ωn0)1(ωn1)1⋮(ωnn−1)1⋯⋯⋱⋯(ωn0)n−1(ωn1)n−1⋮(ωnn−1)n−1⎦⎥⎥⎥⎤
那么 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} D⋅⎣⎢⎢⎢⎡a1a2⋮an⎦⎥⎥⎥⎤=⎣⎢⎢⎢⎡A(x1)A(x2)⋮A(xn)⎦⎥⎥⎥⎤
所以做IDFT,相当于两边同乘 D − 1 mathbf D^{-1} D−1,变成酱紫:
[ 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} ⎣⎢⎢⎢⎡a1a2⋮an⎦⎥⎥⎥⎤=D−1⋅⎣⎢⎢⎢⎡A(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} D−1=⎣⎢⎢⎢⎢⎡n(ωn−0)0n(ωn−1)0⋮n(ωn−(n−1))0n(ωn−0)1n(ωn−1)1⋮n(ωn−(n−1))1⋯⋯⋱⋯n(ωn−0)n−1n(ωn−1)n−1⋮n(ωn−(n−1))n−1⎦⎥⎥⎥⎥⎤
可以发现
( 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} (D⋅D−1)i,j=n∑k=0n−1ωk(i−j)
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) (A∗B)(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)+i⋅B(x))2=A(x)2−B(x)2+2⋅i⋅(A∗B)(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(p−1)/n( p p p满足 n ∣ p − 1 n|p-1 n∣p−1)。
- ω n n ≡ 1 ( m o d p ) omega_n^{n}equiv 1(mod; p) ωnn≡1(modp)
- ω n 0 , ω n 1 , … , ω n n − 1 omega_n^0,omega_n^{1},dots,omega_n^{n-1} ωn0,ωn1,…,ωnn−1在 m o d p mod; p modp意义下互不相同(重要!)
- ω 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/2≡−1(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=1∑naifn−i
每次做完左边,然后用左边贡献右边,再做右边 (废话)。
练习
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=0∑n(s(i)−s(n−i))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=0∑ns(i)2+s(n−i)2−2s(i)s(n−i)=2i=0∑ns(i)2−2i=0∑ns(i)s(n−i)
一个前缀和一个卷积,搞定。
注意:
-
当 f ( n ) f(n) f(n)中的 n n n大于字符串长度时,上面式子中的 i i i不一定能取到边界,需要再判一下。
-
卡精度,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+yx−y,x<y,x≥y
给出序列 { 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 x−y 做的话,是一个 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练习所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复