听了周指导的讲课,一脸懵逼,大概在这之前我连复数的概念都不是很了解。
然后本来准备看各种理解的,因为文化课的缘故,又一直没有整片的时间。
所以希望写一个博客记录一下我学FFT过程中没有理解的,或者自以为比较难理解的部分。
这里是几篇阅读了很有帮助的博客:
那就开始吧。
2019.7.31 再次忘光,准备重学。
发现之前学的时候大概还没学过高中数学,矩阵也不熟练,于是大概就是背了一个板子。
快速傅里叶变换
求解多项式乘法。
即对于两个多项式 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+⋯
对于上面两个式子,最暴力的想法是 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(n\log 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) (0\leq i \leq n) \},\{ x_i,B(x_i) (0\leq 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) (0\leq i \leq n) \} {xi,A(xi)∗B(xi)(0≤i≤n)}
最后转回系数表达式。
至于如何做到 O ( n log n ) O(n\log n) O(nlogn),看到下面就知道,我们取 x i = w n i x_i=w_n^i xi=wni,利用单位根优秀的性质就可以分治来做到了。
之所以写的这么烦,是因为我最开始连FFT可以干什么或者他的大体思路是什么都不知道,就被强行灌输了下面的一堆东西。并不能听懂,of course。
系数表示: 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) (0\leq i < n) \} {xi,A(xi)(0≤i<n)}
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。
再设两个多项式:
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}) \\ \quad\quad=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)\\ \quad\qquad\qquad\qquad=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(k\in N) n=2k(k∈N),不够就高位补0
因为 w n 2 = w n 2 w_{\frac{n}{2}}=w^2_{n} w2n=wn2,所以单位根只要一次预处理出来就好了
上面的公式已经足够做递归版的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&&111\end{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)了。
这一段的矩阵我实在搞不出来,所以借鉴抄了zzd的博客,请看博客顶部链接去%zzd。
拿矩阵来理解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 D\cdot \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
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("%.10f\n", 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)+i\cdot B(x))^2=A(x)^2-B(x)^2+2\cdot i\cdot(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 ,这个多项式的虚部就是答案。
能小小的大大地优化常数。
数论变换,和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)。
所以原根可以完全替代单位根完成NTT。
51nod大数乘法
#include
#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;
}
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
每次做完左边,然后用左边贡献右边,再做右边 (废话)。
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)\\=2\sum_{i=0}^{n}s(i)^2-2\sum_{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太毒瘤了。
题意:
有一个运算
x o p t y = { x + y , x < y x − y , x ≥ y x\;opt\;y=\left\{ \begin{aligned} & x+y &,x
给出序列 { 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 ] mid\in[a_i,b_j] mid∈[ai,bj] 或者 m i d ∈ [ b j , a i ] mid\in[b_j,a_i] mid∈[bj,ai] 的数对的贡献。因为大小关系确定,所以直接 FFT 就好了。
最后 O ( n log 2 n ) O(n\log^2n) O(nlog2n) 。因为 FFT 常数有点大,所以稍稍卡一下常,比如要做 FFT 的两个数组中只要有一个是全空的就不做。
题意:
两个串 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 做吧。