前言
litble不会FFT和NTT,是自己太蒻了。
学习数学知识需要耐心,所以也请和蒟蒻litble一样站在门外的人保持耐心来学,加油吧!
另外,litble很好奇,为什么《数学一本通》讲这些东西只讲了半页纸。
复数
参考博客,同时借了张图。
复数的基本定义
虚数单位: i = − 1 i=\sqrt {-1} i=−1
对于一个数轴上的实数a,让a×(-1)得到-a,那么a就在数轴上旋转了180°。也就是说,a×i×i相当于把a旋转180°,a×i就是把a旋转90°。于是我们就得到了一个美丽的平面:
复数:一个复数x可以表示为
x
=
a
+
b
i
x=a+bi
x=a+bi,当b=0的时候,这个复数为实数。我们可以把一个复数看作以上平面内的一个点(a,b)。其中,
a
a
a叫做复数的实部,
b
b
b叫做虚部。
共轭复数:两个实部相等,虚部为相反数的复数。
模:即复数的绝对值,用向量来表示复数时的长度,量化一下就是 a 2 + b 2 \sqrt {a^2+b^2} a2+b2
复数的三角形式
如图所示,其中
θ
\theta
θ叫做复数的辐角,幅角可以表示为
θ
+
2
k
π
\theta+2k \pi
θ+2kπ的形式,而小于180°的幅角叫做复数的幅角的主值,两个复数相等当且仅当它们的模和幅角的主值相等。
记r为复数x的模,则 x = r ( cos θ + i sin θ ) x=r(\cos \theta +i \sin \theta) x=r(cosθ+isinθ)叫做复数的三角形式。
复数的运算
复数的运算也满足实数的运算法则。
加减
代数式: ( a + b i ) + ( c + d i ) = ( a + c ) + ( b + d ) i (a+bi)+(c+di)=(a+c)+(b+d)i (a+bi)+(c+di)=(a+c)+(b+d)i
三角式:满足矢量运算法则
乘除
代数式: ( a + b i ) ( c + d i ) = a c + b c i + a d i − b d = ( a c − b d ) + ( b c + a d ) i (a+bi)(c+di)=ac+bci+adi-bd=(ac-bd)+(bc+ad)i (a+bi)(c+di)=ac+bci+adi−bd=(ac−bd)+(bc+ad)i
三角式:
r 1 ( cos θ 1 + i sin θ 1 ) r 2 ( cos θ 2 + i sin θ 2 ) = r_1(\cos \theta_1+i \sin \theta_1)r_2(\cos \theta_2+i \sin \theta_2)= r1(cosθ1+isinθ1)r2(cosθ2+isinθ2)=
r 1 r 2 ( cos ( θ 1 + θ 2 ) + sin ( θ 1 + θ 2 ) ) r_1 r_2 (\cos (\theta_1 + \theta_2)+\sin(\theta_1+\theta_2)) r1r2(cos(θ1+θ2)+sin(θ1+θ2))
也就是积的模等于各复数模的积,积的幅角等于各复数幅角的和。
单位根
n次单位根即能够满足方程 ω n = 1 \omega^n=1 ωn=1的复数
由复数的三角式乘除运算法则可以得到,有n个这样的复数,它们分布于平面的单位圆上,并平分这个单位圆。
n次单位根就是: e 2 π k i n , k = 0 , 1 , 2 , . . . , n − 1 e^{\frac{2 \pi ki}{n}},k=0,1,2,...,n-1 en2πki,k=0,1,2,...,n−1
还有一个很牛的公式,叫做欧拉公式:
e θ i = cos θ + i sin θ e^{\theta i}=\cos \theta + i \sin \theta eθi=cosθ+isinθ
就可以知道n次单位根的算数表示法了。记 ω n = e 2 π i n \omega_n=e^{\frac{2 \pi i}{n}} ωn=en2πi
多项式
多项式乘法
给定多项式
A
(
x
)
=
∑
i
=
0
n
a
i
x
i
A(x)=\sum_{i=0}^n a_i x^i
A(x)=∑i=0naixi和
B
(
x
)
=
∑
i
=
0
n
b
i
x
i
B(x)=\sum_{i=0}^n b_i x^i
B(x)=∑i=0nbixi,则它们的积是:
C
(
x
)
=
∑
j
+
k
=
i
,
0
≤
j
,
k
≤
n
a
j
b
k
x
i
C(x)=\sum_{j+k=i,0 \leq j,k \leq n} a_j b_k x^i
C(x)=j+k=i,0≤j,k≤n∑ajbkxi
点值表示法
我们可以取若干个x取值 x 0 , x 1 , . . . {x_0,x_1,...} x0,x1,...,那么多项式 A ( x ) A(x) A(x)的点值表示法就是 A = ( x 0 , A ( x 0 ) ) , ( x 1 , A ( x 1 ) ) . . . A={(x_0,A(x_0)),(x_1,A(x_1))...} A=(x0,A(x0)),(x1,A(x1))...
比如说多项式 A ( x ) = 2 x 2 + x − 1 A(x)=2x^2+x-1 A(x)=2x2+x−1的点值表达式就可以是 A = ( 0 , − 1 ) , ( 1 , 2 ) , ( 2 , 9 ) A={(0,-1),(1,2),(2,9)} A=(0,−1),(1,2),(2,9)
对于一个次数从0到n-1的多项式,要取n个点才能把它确定下来。
有了点值表示法后,多项式乘法就可以化为 A = ( x 0 , A ( x 0 ) ) . . . A={(x_0,A(x_0))...} A=(x0,A(x0))...和 B = ( x 0 , B ( x 0 ) ) . . . B={(x_0,B(x_0))...} B=(x0,B(x0))...的乘积为 C = ( x 0 , A ( x 0 ) B ( x 0 ) ) . . . C={(x_0,A(x_0)B(x_0))...} C=(x0,A(x0)B(x0))...了,现在我们只需要迅速地完成点值表示法与代数表示法之间的转化即可。
快速傅里叶变换
参考博客(没错就是数王dalao的博客)
折半引理
对于n大于0且n为奇数
{
ω
n
2
k
∣
0
≤
k
≤
n
,
k
∈
Z
}
=
{
ω
n
/
2
k
∣
0
≤
k
≤
n
/
2
,
k
∈
Z
}
\{ \omega_n^{2k}|0 \leq k \leq n,k \in Z \}= \{ \omega_{n/2}^k|0 \leq k \leq n/2,k \in Z \}
{ωn2k∣0≤k≤n,k∈Z}={ωn/2k∣0≤k≤n/2,k∈Z}
因为集合要去重嘛。
证明:
( ω n k + n 2 ) 2 = ω n 2 k + n = ω n 2 k ω n n = ( ω n k ) 2 = ω n / 2 k ( \omega_{n}^{k+\frac{n}{2}})^2=\omega_n^{2k+n}=\omega_n^{2k} \omega_n^n=(\omega_n^k)^2=\omega_{n/2}^k (ωnk+2n)2=ωn2k+n=ωn2kωnn=(ωnk)2=ωn/2k
如果上式有哪里不懂的话,我猜你八成是忘了 ω n = e 2 π i n \omega_n=e^{\frac{2 \pi i}{n}} ωn=en2πi这件事了。
快速傅里叶变换
即将多项式快速转化为点值表示法。
首先,对原多项式做奇偶性划分:
A 0 ( x ) = a 0 + a 2 x + a 4 x 2 + . . . + a n − 2 x x 2 − 1 A_0(x)=a_0+a_2x+a_4x^2+...+a_{n-2}x^{\frac{x}{2}-1} A0(x)=a0+a2x+a4x2+...+an−2x2x−1
A 1 ( x ) = a 1 + a 3 x + a 5 x 2 + . . . + a n − 1 x x 2 − 1 A_1(x)=a_1+a_3x+a_5x^2+...+a_{n-1}x^{\frac{x}{2}-1} A1(x)=a1+a3x+a5x2+...+an−1x2x−1
所以 A ( x ) = A 0 ( x 2 ) + x A 1 ( x 2 ) A(x)=A_0(x^2)+xA_1(x^2) A(x)=A0(x2)+xA1(x2)
我们把这个变成点集表达式的时候,可以取一些特殊的x来加速,比如,复数
ω
n
k
\omega_n^k
ωnk
所以
A
(
ω
n
k
)
=
A
0
(
(
ω
n
k
)
2
)
+
ω
n
k
A
1
(
(
ω
n
k
)
2
)
A(\omega_n^k)=A_0((\omega_n^k)^2)+\omega_n^kA_1((\omega_n^k)^2)
A(ωnk)=A0((ωnk)2)+ωnkA1((ωnk)2)
由于 ( e 2 π i n ) 2 k = ( e 4 π i n ) k (e^{\frac{2 \pi i}{n}})^{2k}=(e^{\frac{4 \pi i}{n}})^k (en2πi)2k=(en4πi)k
所以
A
(
ω
n
k
)
=
A
0
(
ω
n
/
2
k
)
+
ω
n
k
A
1
(
ω
n
/
2
k
)
A(\omega_n^k)=A_0(\omega_{n/2}^k)+\omega_n^k A_1(\omega_{n/2}^k)
A(ωnk)=A0(ωn/2k)+ωnkA1(ωn/2k)
又由折半引理,同时
ω
n
k
+
n
2
=
(
e
2
π
i
n
)
k
+
n
2
=
(
e
2
π
i
n
)
k
e
π
i
=
ω
n
k
ω
n
n
2
=
−
ω
n
k
\omega_n^{k+\frac{n}{2}}=(e^{\frac{2 \pi i}{n}})^{k+\frac{n}{2}}=(e^{\frac{2 \pi i}{n}})^k e^{\pi i}=\omega_n^k \omega_n^{\frac{n}{2}}=-\omega_n^k
ωnk+2n=(en2πi)k+2n=(en2πi)keπi=ωnkωn2n=−ωnk
所以得到
A
(
ω
n
k
+
n
/
2
)
=
A
0
(
ω
n
/
2
k
)
−
ω
n
k
A
1
(
ω
n
/
2
k
)
A(\omega_n^{k+n/2})=A_0(\omega_{n/2}^k)-\omega_n^k A_1(\omega_{n/2}^k)
A(ωnk+n/2)=A0(ωn/2k)−ωnkA1(ωn/2k)
当k取遍
[
0
,
n
2
−
1
]
[0,\frac{n}{2}-1]
[0,2n−1]时,
k
+
n
2
k+\frac{n}{2}
k+2n就会取遍
[
n
2
,
n
−
1
]
[\frac{n}{2},n-1]
[2n,n−1]
这样我们可以利用分治,迅速将多项式转化成点值表示法了,复杂度为 O ( n l o g 2 n ) O(n log_2n) O(nlog2n)
逆离散快速傅里叶变换
即将点值表示法快速转化为多项式
这相当于是一个解方程的过程,方程可以写作矩阵(1),下图摘自树王dalao的博客。
所以呢
e
i
j
=
∑
k
=
0
n
−
1
d
i
k
v
k
j
=
∑
k
=
1
n
ω
n
−
i
k
ω
n
k
j
=
∑
k
=
1
n
ω
n
k
(
j
−
i
)
e_{ij}=\sum_{k=0}^{n-1}d_{ik}v_{kj}=\sum_{k=1}^n\omega_n^{-ik}\omega_n^{kj}=\sum_{k=1}^n\omega_n^{k(j-i)}
eij=∑k=0n−1dikvkj=∑k=1nωn−ikωnkj=∑k=1nωnk(j−i)
然后当i=j时,显然 e i j = n e_{ij}=n eij=n
当i不等于j时,显然 e i j = ∑ k = 0 n − 1 ω n ( j − i ) k e_{ij}=\sum_{k=0}^{n-1} \omega_n^{(j-i)k} eij=∑k=0n−1ωn(j−i)k,然后根据等比数列随便搞一搞得到:
e i j = ( ω n n ) j − i − 1 ω n j − i − 1 = 0 e_{ij}=\frac{(\omega_n^n)^{j-i}-1}{\omega_n^{j-i}-1}=0 eij=ωnj−i−1(ωnn)j−i−1=0
所以
1
n
E
n
=
I
n
\frac{1}{n} E_n=I_n
n1En=In(
I
n
I_n
In表示n×n的单位矩阵),也就是
1
n
D
=
V
−
1
\frac{1}{n}D=V^{-1}
n1D=V−1
那么:
而这个D矩阵真是完美,可以和类似于快速傅里叶变换的方法搞定,就大大减小了代码复杂度
蝶形算法
例题:洛谷P3803
又名蝴蝶操作,Cooley-Tukey算法(其实就是个迭代)。
这个名字非常优美,这个算法也很优美。
我们手动模拟一个递归过程,每次把偶数位放在前面,奇数位放在后面处理。第一行和第二行表示当前位置的数的二进制形式。那么(下表依旧摘抄自数王dalao):
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{matrix} &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{matrix} 000 0 0 0 0 000 001 1 2 4 4 100 010 2 4 2 2 010 011 3 6 6 6 110 100 4 1 1 1 001 101 5 3 5 5 101 110 6 5 3 3 011 1117777111
发现位置最后的数,是其二进制反过来后得到的数。其实证明也很容易,我们因为按照奇偶划分,会把末尾是1的放后面,末尾是0的放前面。然后把倒数第二位是0的放前面,是1的放后面…算法步骤:
1. 将二进制反序后的序列处理出来。
2. 枚举当前处理的区间长度 2 i 2i 2i(至于为什么要乘2,因为代码里乘了2,此处i不是虚数单位)
3. 根据欧拉公式计算 ω 2 i \omega_{2i} ω2i
4. 枚举每个区间开头j
5. 枚举该区间的每个偶数位那一半的 a j + k a_{j+k} aj+k和奇数位那一半的 a i + j + k a_{i+j+k} ai+j+k
6. 令 t 1 = a j + k t1=a_{j+k} t1=aj+k, t 2 = ω 2 i k a i + j + k t2=\omega_{2i}^k a_{i+j+k} t2=ω2ikai+j+k,那么更改 a j + k = t 1 + t 2 a_{j+k}=t1+t2 aj+k=t1+t2, a i + j + k = t 1 − t 2 a_{i+j+k}=t1-t2 ai+j+k=t1−t2。由于这个操作的流程像蝴蝶的翅膀,所以该算法称为蝶形算法。
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define db double
const int N=2650000;//注意空间
const db pi=3.1415926535897384626;
struct com{db r,i;}a[N],b[N];//复数
int n,m,len,rev[N];
com operator + (com A,com B) {return (com){A.r+B.r,A.i+B.i};}
com operator - (com A,com B) {return (com){A.r-B.r,A.i-B.i};}
com operator * (com A,com B) {return (com){A.r*B.r-A.i*B.i,A.i*B.r+A.r*B.i};}
com operator / (com A,db B) {return (com){A.r/B,A.i/B};}
void FFT(com *a,int x) {
for(int i=0;i<n;++i) if(i<rev[i]) swap(a[i],a[rev[i]]);//防止两次交换
for(int i=1;i<n;i<<=1) {//步骤2
com wn=(com){cos(pi/i),x*sin(pi/i)};//步骤3
for(int j=0;j<n;j+=(i<<1)) {//枚举区间开头,步骤4
com w=(com){1,0},t1,t2;
for(int k=0;k<i;++k,w=w*wn)//步骤5
t1=a[j+k],t2=w*a[j+k+i],a[j+k]=t1+t2,a[j+k+i]=t1-t2;
}
}
if(x==-1)for(int i=0;i<n;i++)a[i]=a[i]/n;
}
int main() {
scanf("%d%d",&n,&m);
for(int i=0;i<=n;++i) scanf("%lf",&a[i].r);
for(int i=0;i<=m;++i) scanf("%lf",&b[i].r);
m=n+m; for(n=1;n<=m;n<<=1) ++len;
for(int i=0;i<n;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<(len-1));//二进制下的翻转,步骤1
FFT(a,1),FFT(b,1);
for(int i=0;i<=n;++i) a[i]=a[i]*b[i];
FFT(a,-1);
for(int i=0;i<=m;++i) printf("%d ",(int) (a[i].r+0.5));
return 0;
}
原根
小L学会FFT后,自以为非常牛逼,于是要dalao给她道题水水。dalao随手给了她一道…
卡精度的题。
在快速傅里叶变换里,我们利用 ω n \omega_n ωn实现了对于消去引理和折半引理的满足。那么,有没有什么整数也满足呢?
原根就满足。
what is 原根 you may ask
定义P的原根为满足 g ϕ ( P ) ≡ 1 (   m o d   P ) g^{\phi(P)} \equiv 1 (\bmod P ) gϕ(P)≡1(modP)的整数g,请记住这个定义。
对于一个质数 P,如果它存在原根,那么 g i ≠ g j g^i ≠ g^j gi̸=gj。由于这条性质,所以我们可以利用原根来生成点集表示法。
快速数论变换
现在我们用 g ϕ ( P ) n g^{\frac{\phi(P)}{n}} gnϕ(P)来代替 ω n \omega_n ωn进行计算。
对于P的要求:要求 ϕ ( P ) n \frac{\phi(P)}{n} nϕ(P)为整数,又因为当P为质数时, ϕ ( P ) = P − 1 \phi(P)=P-1 ϕ(P)=P−1,所以 P = k 2 q + 1 P=k2^q+1 P=k2q+1,其中 2 q ≥ n 2^q \geq n 2q≥n
原根是否可以代替单位根,取决于FFT的引理,原根是否也满足,以折半引理为例,在模P的意义下:
( g ϕ ( P ) n ) 2 = g 2 ϕ ( P ) n = g ϕ ( P ) n / 2 (g^{\frac{\phi(P)}{n}})^2=g^{\frac{2\phi(P)}{n}}=g^{\frac{\phi(P)}{n/2}} (gnϕ(P))2=gn2ϕ(P)=gn/2ϕ(P)
( g ϕ ( P ) n ) k + n 2 = g ϕ ( P ) 2 ( g ϕ ( P ) n ) k = − ( g ϕ ( P ) n ) k (g^{\frac{\phi(P)}{n}})^{k+\frac{n}{2}}=g^{\frac{\phi(P)}{2}}(g^{\frac{\phi(P)}{n}})^k=-(g^{\frac{\phi(P)}{n}})^k (gnϕ(P))k+2n=g2ϕ(P)(gnϕ(P))k=−(gnϕ(P))k
所以是满足的,其他的东西推导也类似啦。
那么怎么求原根呢?用bsgs打表。除非题目给你模数,否则可以一劳永逸地选择几个记住:一位dalao的归纳(998244353是个好东西)
如果P不是个质数,就用中国剩余定理玩合并。
但是以上两个过程都比较痛苦就是了…给出NTT模板的代码,例题还是上面那道:
#include<bits/stdc++.h>
using namespace std;
const int G=3,mod=(119<<23)+1,N=2650000;
int n,m,len,a[N],b[N],rev[N];
int ksm(int x,int y) {
int re=1;
while(y) {
if(y&1) re=1LL*re*x%mod;
x=1LL*x*x%mod,y>>=1;
}
return re;
}
void NTT(int *a,int x) {
for(int i=0;i<n;++i) if(i<rev[i]) swap(a[i],a[rev[i]]);
for(int i=1;i<n;i<<=1) {
int gn=ksm(G,(mod-1)/(i<<1));
for(int j=0;j<n;j+=(i<<1)) {
int t1,t2,g=1;
for(int k=0;k<i;++k,g=1LL*g*gn%mod) {
t1=a[j+k],t2=1LL*g*a[j+k+i]%mod;
a[j+k]=(t1+t2)%mod,a[j+k+i]=(t1-t2+mod)%mod;
}
}
}
if(x==1) return;
int ny=ksm(n,mod-2); reverse(a+1,a+n);//在FFT里面,我们已经利用x在步骤3中完成了翻转。
for(int i=0;i<n;++i) a[i]=1LL*a[i]*ny%mod;
}
int main() {
scanf("%d%d",&n,&m);
for(int i=0;i<=n;++i) scanf("%d",&a[i]);
for(int i=0;i<=m;++i) scanf("%d",&b[i]);
m=n+m;for(n=1;n<=m;n<<=1)++len;
for(int i=0;i<n;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<(len-1));
NTT(a,1),NTT(b,1);
for(int i=0;i<n;++i) a[i]=1LL*a[i]*b[i]%mod;
NTT(a,-1);for(int i=0;i<=m;++i) printf("%d ",a[i]);
return 0;
}
应用
bzoj4503
将某一元素翻转来构成卷积,是一种常用手段。那么我们将B串翻转,字母全部转化为数字,"?"转化为0之后,我们得到A从位置j开始能和B匹配的要求是:
C j + m − 1 = ∑ i = 0 m − 1 ( a j + i − b m − i − 1 ) 2 ∗ b m − 1 − i = 0 C_{j+m-1}=\sum_{i=0}^{m-1}(a_{j+i}-b_{m-i-1})^2*b_{m-1-i}=0 Cj+m−1=i=0∑m−1(aj+i−bm−i−1)2∗bm−1−i=0
展开发现是三个卷积的和,分别用FFT或者NTT搞一搞,查找为0的C值即可。
bzoj4827/洛谷P3723
假设增加的值为C,差异值公式:
∑ i = 0 n − 1 ( a i + C − b i ) 2 = ∑ i = 0 n − 1 a i + ∑ i = 0 n − 1 b i + n C 2 − 2 ∑ i = 0 n − 1 a i b i + 2 C ∑ i = 0 n − 1 ( a i − b i ) \sum_{i=0}^{n-1}(a_i+C-b_i)^2=\sum_{i=0}^{n-1}a_i+\sum_{i=0}^{n-1}b_i+nC^2-2\sum_{i=0}^{n-1}a_ib_i+2C\sum_{i=0}^{n-1}(a_i-b_i) i=0∑n−1(ai+C−bi)2=i=0∑n−1ai+i=0∑n−1bi+nC2−2i=0∑n−1aibi+2Ci=0∑n−1(ai−bi)
假定C为一个常数,那么这个式子有一堆的常数,主要矛盾是求最大的 2 ∑ i = 0 n − 1 a i b i 2\sum_{i=0}^{n-1}a_ib_i 2∑i=0n−1aibi,那么将B手链翻转后用FFT或NTT搞一搞即可。
最后,可以枚举C或者利用二次函数搞定和C有关的最优值。
bzoj3527/洛谷P3338
可得
E
i
=
∑
j
=
1
i
−
1
q
j
(
i
−
j
)
2
−
∑
j
=
i
+
1
n
q
j
(
i
−
j
)
2
E_i=\sum_{j=1}^{i-1} \frac{q_j}{(i-j)^2}-\sum_{j=i+1}^n\frac{q_j}{(i-j)^2}
Ei=j=1∑i−1(i−j)2qj−j=i+1∑n(i−j)2qj
令
a
i
=
q
i
a_i=q_i
ai=qi,
b
i
=
1
i
2
b_i=\frac{1}{i^2}
bi=i21,可以发现
q
j
(
i
−
j
)
2
=
a
j
b
i
−
j
\frac{q_j}{(i-j)^2}=a_jb_{i-j}
(i−j)2qj=ajbi−j
原式就是两个卷积相减,后一个卷积需要反向来构造。
bzoj3771
首先是一个基本的生成函数,比如说有一把斧头的权值为 w i w_i wi,那么就令多项式里面的 x w i x^{w_i} xwi的权值加1,这样弄出来的一个多项式称为A。
比如选两把斧头,那么就是 A ∗ A A*A A∗A,k次项的系数就是方案数,不过还要去除都选同一把斧头的情况。
那么设把斧头权值都乘以2得到的多项式为B,乘以3得到的为C,根据容斥原理,答案就是求:
A
+
A
2
−
B
2
+
A
3
−
3
A
B
+
2
C
6
A+\frac{A^2-B}{2}+\frac{A^3-3AB+2C}{6}
A+2A2−B+6A3−3AB+2C
bzoj4332
首先令t(0)=0, t ( i ) = O i 2 + S i + U t(i)=Oi^2+Si+U t(i)=Oi2+Si+U,然后设 g ( i , j ) g(i,j) g(i,j)为i个小朋友,每人都有糖的开心程度积(小朋友个数还是设为n吧,显然m小于n的时候n取m即可)。
g ( i , j ) = ∑ k = 0 j g ( i − 1 , k ) t ( j − k ) g(i,j)=\sum_{k=0}^j g(i-1,k)t(j-k) g(i,j)=k=0∑jg(i−1,k)t(j−k)
发现这是一个卷积的形式,可得: g ( i ) = t i g(i)=t^i g(i)=ti
我们要求的答案是 ∑ i = 1 n g ( i , m ) \sum_{i=1}^n g(i,m) ∑i=1ng(i,m)
于是设 f ( n ) = ∑ i = 1 n g ( i ) f(n)=\sum_{i=1}^n g(i) f(n)=∑i=1ng(i),那么 f ( n , m ) f(n,m) f(n,m)就是答案。
考虑分治求解, f ( n ) = f ( n 2 ) + ∑ i = n 2 + 1 n g ( i ) = f ( n 2 ) + t n 2 f ( n 2 ) f(n)=f(\frac{n}{2})+\sum_{i=\frac{n}{2}+1}^{n} g(i)=f(\frac{n}{2})+t^{\frac{n}{2}}f(\frac{n}{2}) f(n)=f(2n)+∑i=2n+1ng(i)=f(2n)+t2nf(2n)
对于奇数的n,可以首先求出 f ( n − 1 ) f(n-1) f(n−1)然后再算。
取模就每次算完多项式乘法后模一下。
复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)