FFT理性瞎扯
xtx1092515503
2021-06-29 08:55:35
> 大家好,这里是FFT理性瞎扯。
>
> 什么?你问[这](https://www.luogu.com.cn/blog/Troverld/fft-gan-xing-xia-che)是什么?
>
> 啊,那是FFT感性瞎扯。
>
> 也就是说,有一份感性瞎扯和一份理性瞎扯?
>
> 没错!上方链接中给出的,是很久以前我写的,近似于黑历史的粗浅的FFT感性瞎扯。而这份,是不那么像黑历史的不那么粗浅的FFT理性瞎扯。
>
> 实际上,前一份感性瞎扯注重的是FFT的器用,类似于黑匣子原理解说,而这份理性瞎扯更注重于FFT的本质。
>
> 也因此,更多奇奇怪怪的卷积科技,也会一起被塞在这篇被不知道哪个蠢货一拍大腿就题名为“FFT理性瞎扯”的学习笔记里吧。
>
> 什么?你问具体有哪些科技?
>
> ……目前是打算把循环卷积、单位根反演、Bluestein等东西全部加进去吧。
>
> 加完了吗?
>
> (……咕咕咕)
# I.DFT&IDFT的本质
DFT的本质是什么?
是变换:$f(x)=\sum\limits_{i=0}^{n-1}f_ix^i\rightarrow\hat f(x)=\sum\limits_{i=0}^{n-1}x^i\sum\limits_{j=0}^{n-1}f_j\omega_n^{ij}$。
也是变换:$\begin{bmatrix}f_0\\f_1\\\vdots\\f_{n-1}\end{bmatrix}\rightarrow\begin{bmatrix}f_0\\f_1\\\vdots\\f_{n-1}\end{bmatrix}\begin{bmatrix}1&1&\dots&1\\1&\omega_n^1&\dots&\omega_n^{n-1}\\\vdots&\vdots&\ddots&\vdots\\1&\omega_n^{n-1}&\dots&\omega_n^{(n-1)^2}\end{bmatrix}$
其中,$\omega_n$ 是 $n$ 次单位根,实质是虚数 $\cos\dfrac{2\pi}{n}+i\sin\dfrac{2\pi}{n}$。
这其中,我们令转移矩阵 $G=\begin{bmatrix}1&1&\dots&1\\1&\omega_n^1&\dots&\omega_n^{n-1}\\\vdots&\vdots&\ddots&\vdots\\1&\omega_n^{n-1}&\dots&\omega_n^{(n-1)^2}\end{bmatrix}$。同时,我们将多项式 $f$ 的向量形式记作 $\mathbf f$。则有变换本质是 $\mathbf f\rightarrow\mathbf fG=\hat{\mathbf f}$。
假如这里是感性瞎扯,我就会不厌其烦地分析它的复杂度、实现方法之类的吧。
可惜,这里是理性瞎扯,因此就没有愉快的OI环节了。
IDFT的本质是什么?
是变换 $\hat f(x)=\sum\limits_{i=0}^{n-1}x^i\sum\limits_{j=0}^{n-1}f_j\omega_n^{ij}\rightarrow f(x)=\sum\limits_{i=0}^{n-1}f_ix^i$。
也是 $\hat{\mathbf f}\rightarrow\hat{\mathbf f}G^{-1}=\mathbf f$。
这里我们引入**范德蒙德方阵**的概念:序列 $\{x_0,x_1,\dots,x_{n-1}\}$ 对应的范德蒙德方阵的定义如下:
$$V(\{x\})=\begin{bmatrix}1&1&\dots&1\\x_0&x_1^1&\dots&x_n^{n-1}\\\vdots&\vdots&\ddots&\vdots\\x_0^{n-1}&x_1^{n-1}&\dots&x_{n-1}^{n-1}\end{bmatrix}$$
则,$G=V(\{1,\omega_n,\omega_n^2,\dots,\omega_n^{n-1}\})$。
可以发现,范德蒙德方阵的实质就是多点求值,而其逆就是插值吧。
DFT的实现是什么?
那套分治理论,应该很好理解吧。
IDFT的实现是什么?
恐怕没几个人会能理解,其与DFT的唯一区别就在于单位根要取倒数并在搞完后除以 $n$ 的原因吧。
但是,借用范德蒙德方阵的理论,会发现假如DFT的作用是用 $G$ 乘上 $\mathbf f$ 的话,IDFT的作用就是 $G^{-1}$ 乘上 $\mathbf f$ 罢。
然后,我们IDFT的实操,明显就是使用了 $\dfrac{V(1,\omega_n^{-1},\omega_n^{-2},\dots,\omega_n^{-(n-1)})}n$ 这个矩阵吧。
那么,是否有$G^{-1}=\dfrac{V(1,\omega_n^{-1},\omega_n^{-2},\dots,\omega_n^{-(n-1)})}n$ 呢?
我们就来得出 $G^{-1}$ 到底是什么吧。
考虑拉格朗日插值。
$$f=\sum\limits_{i=0}^{n-1}\hat f_i\prod\limits_{j\neq i}\dfrac{x-\omega_n^j}{\omega_n^i-\omega_n^j}$$
稍微变换一下
$$f=\sum\limits_{i=0}^{n-1}\hat f_i\dfrac{\prod\limits_{j\neq i}x-\omega_n^j}{\prod\limits_{j\neq i}\omega_n^i-\omega_n^j}$$
这时候可以考虑快速插值时的分析方法。考虑分子的东西,首先令
$$G(x)=\prod\limits_{0\leq j<n}x-\omega_n^j$$
这时候,通过暴力展开/理性分析/猜,我们都可以得出 $G(x)=x^n-1$,因为方程 $G(x)=0$ 显然有 $n$ 个根 $\{1,\omega_n,\omega_n^2,\dots,\omega_n^{n-1}\}$,而这些根全都是方程 $x^n-1=0$ 的根,于是就得出 $G(x)\propto x^n-1$,而通过展开最高项得出正比例系数为 $1$,于是就有 $G(x)=x^n-1$。
然后关于分子上的东西就有
$$\begin{aligned}\prod\limits_{j\neq i}x-\omega_n^j&=\dfrac{G(x)}{x-\omega_n^i}\\&=\dfrac{x^n-1}{x-\omega_n^i}\\&=\dfrac{1}{\omega_n^i-x}-\dfrac{x^n}{\omega_n^i-x}\\&=\omega_n^{-i}\times\Big(\dfrac{1}{1-\omega_n^{-i}x}-x^n\times\dfrac{1}{1-\omega_n^{-i}x}\Big)\\&=\omega_n^{-i}\times\Big(\sum\limits_{j=0}\omega_n^{-ij}x^j-x^n\sum\limits_{j=0}\omega_n^{-ij}x^j\Big)&(\text{展开成数列形式})\\&=\omega_n^{-i}\times\Big(\sum\limits_{j=0}\omega_n^{-ij}x^j-\sum\limits_{j=n}\omega_n^{-i(j-n)}x^j\Big)\\&=\omega_n^{-i}\times\Big(\sum\limits_{j=0}\omega_n^{-ij}x^j-\sum\limits_{j=n}\omega_n^{-ij}x^j\Big)&(\omega_n^n=1)\\&=\omega_n^{-i}\times\sum\limits_{j=0}^{n-1}\omega_n^{-ij}x^j\end{aligned}$$
然后关于分母上的东西就有
$$\prod\limits_{j\neq i}\omega_n^i-\omega_n^j=\lim\limits_{x\rightarrow\omega_n^i}\dfrac{G(x)}{x-\omega_n^i}\xlongequal{\text{l'H(此符号表示洛必达法则)}}G'(\omega_n^i)=n\omega_n^{i(n-1)}=n\omega_n^{-i}$$
于是
$$f=\sum\limits_{i=0}^{n-1}\hat f_i\dfrac{\omega_n^{-i}\times\sum\limits_{j=0}^{n-1}\omega_n^{-ij}x^j}{n\omega_n^{-i}}=\sum\limits_{i=0}^{n-1}\hat f_i\dfrac{\sum\limits_{j=0}^{n-1}\omega_n^{-ij}x^j}n$$
我们发现,其就是 $\dfrac{V(1,\omega_n^{-1},\omega_n^{-2},\dots,\omega_n^{-(n-1)})}n$ 这个矩阵。
于是我们便理解了IDFT的本质。
# II.FFT的本质
FFT的本质是什么?
是卷积。
再说一遍?
是……是循环卷积。
没错,假如我们对两个长度为 $n$ 的多项式用FFT相乘,若DFT只展开到长度为 $n$,则IDFT时作用的就也是一个长度为 $n$ 的序列,但实际上的卷积长度可是 $2n-1$。一个 $n$ 个点的点值序列,怎么想都不可能插出一个长度为 $2n-1$ 的多项式,只有可能插出长度为 $n$ 的多项式。
那么,那些更高的位跑哪去了呢?
我们发现,因为DFT $n$ 位时用的是 $\omega_n$ 作为单位根,所以IDFT时,对于高于 $n$ 的位,有 $\omega_n^{i}=\omega_n^{i\bmod n}$。也就是说,这实际上是循环卷积。
于是,若 $h=f\times g$,但是这里的 $\times$ 是FFT展开 $n$ 位的乘法,则有 $h_i=\sum\limits_{j+k\equiv i\pmod n}f_j\times g_k$。
只是在普通卷积的时候,我们FFT的数组开得很长,所以不需要担心是否循环起来了而已。
# III.单位根反演
此章节特别鸣谢:[command-block神仙的博客](https://www.luogu.com.cn/blog/command-block/dan-wei-gen-fan-yan-xiao-ji)。
$$\boxed{[n|a]=\dfrac{\sum_{i=0}^{n-1}\omega_n^{ai}}{n}}$$
> 证:
>
> 考虑等比数列求和。当 $n\nmid a$ 时,$\sum\limits_{i=0}^{n-1}\omega_n^{ai}=\dfrac{1-\omega_n^{an}}{1-\omega_n^{a}}=\dfrac{1-1}{1-\omega_n^a}$,因在任何情形下分母均非零,故此式恒为 $0$。而当 $n|a$ 时,此式显然为 $1$。
推论:
$$\boxed{[a\equiv b\pmod n]=\dfrac{\sum_{i=0}^{n-1}\omega_n^{(a-b)i}}{n}}$$
## III.I.[[LOJ#6485]LJJ 学二项式定理](https://loj.ac/p/6485)
$$[i\equiv j\pmod4]=\dfrac{\sum\limits_{k=0}^3\omega_4^{(i-j)k}}4$$
于是
$$\begin{aligned}\sum\limits_{i=0}^n\dbinom ni\times s^i\times a_{i\bmod 4}&=1/4\times\sum\limits_{j=0}^3\sum\limits_{i=0}^n\dbinom nis^ia_j\sum\limits_{k=0}^3\omega_4^{(i-j)k}\\&=1/4\times\sum\limits_{j=0}^3\sum\limits_{k=0}^3a_j\omega_4^{-jk}\sum\limits_{i=0}^n\dbinom nis^i\omega_4^{ik}\\&=1/4\times\sum\limits_{j=0}^3\sum\limits_{k=0}^3a_j\omega_4^{-jk}(s\omega_4^k+1)^n\end{aligned}$$
$\omega_4$ 在 $\bmod998244353$ 意义下存在。故直接算即可。
代码:
```cpp
#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
const int phi=mod-1;
int ksm(int x,int y){int z=1;for(;y;y>>=1,x=1ll*x*x%mod)if(y&1)z=1ll*z*x%mod;return z;}
const int G=3;
const int w=ksm(G,(mod-1)/4);
typedef long long ll;
int T,s,res,a[4];
ll n;
int main(){
scanf("%d",&T);
while(T--){
scanf("%lld%d",&n,&s),res=0;
n%=phi;
for(int i=0;i<4;i++)scanf("%d",&a[i]);
for(int j=0;j<4;j++)for(int k=0;k<4;k++)(res+=1ll*a[j]*ksm(w,phi-j*k)%mod*ksm((1ll*s*ksm(w,k)+1)%mod,n)%mod)%=mod;
res=1ll*res*ksm(4,phi-1)%mod;
printf("%d\n",res);
}
return 0;
}
```
## III.II.[小猪佩奇学数学](https://www.luogu.com.cn/problem/P5591)
$$\begin{aligned}&\sum_{i=0}^n\dbinom nip^i\left\lfloor\dfrac{i}{k}\right\rfloor\\=&\sum_{i=0}^n\dbinom nip^i\times\dfrac{i-i\bmod k}{k}&(\text{整除的实际意义})\\=&\dfrac{\sum\limits_{i=0}^n\dbinom nip^ii-\sum\limits_{i=0}^n\dbinom nip^i(i\bmod k)}{k}\\=&\dfrac1k\Bigg(\sum_{i=1}^n\dbinom{n-1}{i-1}np^i-\sum_{i=0}^n\dbinom nip^i\sum_{j=0}^{k-1}j[i\bmod k=j]\Bigg)&(\text{左式使用了吸收恒等式}\dbinom nmm=\dbinom{n-1}{m-1}n)\\=&\dfrac1k\Bigg(np\sum_{i=0}^{n-1}\dbinom{n-1}{i}p^i-\sum_{i=0}^n\dbinom nip^i\sum_{j=0}^{k-1}j\times\dfrac{\sum\limits_{d=0}^{k-1}\omega_k^{(i-j)d}}k\Bigg)\\=&\dfrac1k\Bigg(np(p+1)^{n-1}-\dfrac1k\sum\limits_{d=0}^{k-1}\sum_{j=0}^{k-1}j\omega_k^{-jd}\sum_{i=0}^n\dbinom nip^i\omega_k^{id}\Bigg)\\=&\dfrac1k\Bigg(np(p+1)^{n-1}-\dfrac1k\sum\limits_{d=0}^{k-1}(1+p\omega_k^d)^n\sum_{j=0}^{k-1}j\omega_k^{-jd}\Bigg)\end{aligned}$$
这时候,考虑令 $A=\sum\limits_{j=0}^{k-1}j\omega_k^{-jd}$。
则 $\omega_k^{-d}A=\sum\limits_{j=0}^{k-1}j\omega_k^{-(j+1)d}$。
二者相减,得到 $(1-\omega_k^{-d})A=\sum\limits_{j=0}^{k-1}j\omega_{k}^{-jd}-\sum\limits_{j=1}^k(j-1)\omega^{-jd}=\sum\limits_{j=1}^{k-1}\omega_k^{-jd}-(k-1)\omega_k^{-kd}$。
显然,有 $\omega_k^{-kd}=1$,则有 $(1-\omega_k^{-d})A=\sum\limits_{j=1}^{k-1}\omega_k^{-jd}-k+1$。
显然,有 $\omega_k^{0}=1$。于是有 $(1-\omega_k^{-d})A=\sum\limits_{j=0}^{k-1}\omega_k^{-jd}-k$。
套上等比数列求和,得到 $(1-\omega_k^{-d})A=\dfrac{1-\omega_k^{-kd}}{1-\omega_k^{-d}}-k$。
显然,又出现了 $\omega_k^{-kd}=1$,则有 $(1-\omega_k^{-d})A=-k$。于是 $A=\dfrac{k}{\omega_k^{-d}-1}$。
带回原式可得
$$\begin{aligned}&\sum_{i=0}^n\dbinom nip^i\left\lfloor\dfrac{i}{k}\right\rfloor\\=&\dfrac1k\Bigg(np(p+1)^{n-1}-\dfrac1k\sum\limits_{d=0}^{k-1}(1+p\omega_k^d)^n\sum_{j=0}^{k-1}j\omega_k^{-jd}\Bigg)\\=&\dfrac1k\Bigg(np(p+1)^{n-1}-\dfrac1k\sum\limits_{d=0}^{k-1}(1+p\omega_k^d)^n\times\dfrac{k}{\omega_k^{-d}-1}\Bigg)\\=&\dfrac1k\Bigg(np(p+1)^{n-1}-\sum\limits_{d=0}^{k-1}\dfrac{(1+p\omega_k^d)^n}{\omega_k^{-d}-1}\Bigg)\end{aligned}$$
这时候,我们看向数据范围,就会发现我们可以枚举这个 $d$ 计算。这样,时间复杂度 $O(k\log n)$。
**但是,需要注意的是!**
当 $\omega_k^{-d}=1$ 时,我们上述关于 $A$ 的分析是不成立的。
这时候,我们重新看向 $A$,发现此时有 $A=\sum\limits_{j=0}^{k-1}j=\dfrac{(k-1)k}2$。
这时就需要特判一下。
代码:
```cpp
#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
int ksm(int x,int y=mod-2){int z=1;for(;y;y>>=1,x=1ll*x*x%mod)if(y&1)z=1ll*z*x%mod;return z;}
const int G=3;
int n,p,k,w,res;
int main(){
scanf("%d%d%d",&n,&p,&k),w=ksm(G,(mod-1)/k);
for(int d=0,W=1;d<k;d++,W=1ll*W*w%mod)(res+=1ll*ksm((1ll*W*p+1)%mod,n)*(W!=1?ksm((ksm(W)+mod-1)%mod):1ll*(k-1)*ksm(2)%mod)%mod)%=mod;
printf("%d\n",(1ll*n*p%mod*ksm(p+1,n-1)%mod+mod-res)%mod*ksm(k)%mod);
return 0;
}
```
## III.III.[[UOJ#450][集训队作业2018]复读机](https://uoj.ac/problem/450)
- $d=1$
非常显然,答案即为 $k^n$。
- 其余情况
这时就要用EGF了。
单个颜色的EGF是 $f(x)=\sum\limits_{d|i}\dfrac{x^i}{i!}$。
全部的EGF是 $f^k$。
则要求的就是 $n!f^k_n$。
但是 $f$ 的封闭形式难搞,连带着 $f^k$ 的封闭形式也难搞。
咋办呢?
单位根反演。
$$[d|i]=\dfrac{\sum\limits_{j=0}^{d-1}\omega_d^{ij}}{d}$$
带进去就得到
$$f(x)=\dfrac1d\sum\limits_{i=0}\dfrac{\sum\limits_{j=0}^{d-1}\omega_d^{ij}}{i!}x^i=\dfrac1d\sum\limits_{j=0}^{d-1}\sum\limits_{i=0}\dfrac{(x\omega_d^j)^i}{i!}=\dfrac1d\sum\limits_{j=0}^{d-1}e^{x\omega_d^j}$$
- $d=2$
$$f(x)=\dfrac{e^x+e^{-x}}{2}$$
则答案为 $n![x^n]\Big(\dfrac{e^x+e^{-x}}2\Big)^k$。
二项式展开得到 $\dfrac{n!}{2^k}[x^n]\sum\limits_{i=0}^k\dbinom kie^{ix}\times e^{-(k-i)x}=\dfrac{n!}{2^k}[x^n]\sum\limits_{i=0}^k\dbinom kie^{(2i-k)x}$。
提取系数得到 $\dfrac{\sum\limits_{i=0}^k\dbinom ki(2i-k)^n}{2^k}$。
$k\log n$ 直接算即可。
- $d=3$。
$$f(x)=\dfrac{e^x+e^{\omega_3x}+e^{\omega_3^2x}}{3}$$。
答案为 $n![x^n]\Big(\dfrac{e^x+e^{\omega_3x}+e^{\omega_3^2x}}{3}\Big)^k$。
……展开!
$$\dfrac{n![x^n]\sum\limits_{i=0}^k\sum\limits_{j=0}^{k-i}\dbinom ki\dbinom{k-i}je^{ix}\times e^{j\omega_3x}\times e^{(k-i-j)\omega_3^2x}}{3^k}$$
……提取!
$$\dfrac{\sum\limits_{i=0}^k\sum\limits_{j=0}^{k-i}\dbinom ki\dbinom{k-i}j\Big(i+j\omega_3+(k-i-j)\omega_3^2\Big)^n}{3^k}$$
$k^2\log n$ 直接算即可。
代码:
```cpp
#include<bits/stdc++.h>
using namespace std;
const int mod=19491001;
const int w=663067;
int n,k,d,res,fac[500100],inv[500100];
int ksm(int x,int y=mod-2){int z=1;for(;y;y>>=1,x=1ll*x*x%mod)if(y&1)z=1ll*z*x%mod;return z;}
int main(){
scanf("%d%d%d",&n,&k,&d);
if(d==1)return printf("%d\n",ksm(k,n)),0;
fac[0]=1;for(int i=1;i<=k;i++)fac[i]=1ll*fac[i-1]*i%mod;
inv[k]=ksm(fac[k]);for(int i=k;i;i--)inv[i-1]=1ll*inv[i]*i%mod;
if(d==2){
for(int i=0;i<=k;i++)(res+=1ll*fac[k]*inv[i]%mod*inv[k-i]%mod*ksm((2*i-k+mod)%mod,n)%mod)%=mod;
res=1ll*res*ksm(ksm(2,k))%mod;
}else{
for(int i=0;i<=k;i++)for(int j=0;i+j<=k;j++)(res+=1ll*fac[k]*inv[i]%mod*inv[j]%mod*inv[k-i-j]%mod*ksm((i+1ll*j*w+1ll*(k-i-j)*ksm(w,2))%mod,n)%mod)%=mod;
res=1ll*res*ksm(ksm(3,k))%mod;
}
printf("%d\n",res);
return 0;
}
```
# IV.循环卷积理论(`Bluestein`算法)
怎么循环卷积?
如果 $n$ 是 $2$ 的整数次幂的话,那么直接FFT就行了吧。
那如果 $n$ 不是 $2$ 的整数次幂呢?
那就正常开两倍数组,然后手动循环吧。
这样如果太慢了呢?
……
$$\begin{aligned}h_i&=\sum_{j+k\equiv i\pmod n}f_j\times g_k\\&=\dfrac1n\sum\limits_{j,k}f_jg_k\sum\limits_{t=0}^{n-1}\omega_n^{(j+k-i)t}\\&=\dfrac1n\sum\limits_{t=0}^{n-1}\omega_n^{-it}\sum\limits_{j,k}f_j\omega_n^{jt}g_k\omega_n^{kt}\end{aligned}$$
……然后发现这就从另一种形式上证出了DFT和IDFT的原理。
考虑DFT的式子:
$$\hat f(x)=\sum\limits_{i=0}^{n-1}x^i\sum\limits_{j=0}^{n-1}f_j\omega_n^{ij}$$
我们考虑一些东西来凑出 $ij$。
$$\dfrac{i^2+j^2-(i-j)^2}2=ij$$
代入得到
$$\hat f(x)=\sum\limits_{i=0}^{n-1}x^i\sum\limits_{j=0}^{n-1}f_j\omega_n^{\big(i^2+j^2-(i-j)^2\big)/2}$$
又有 $\omega_n^k=\omega_{2n}^{2k}$。于是有
$$\hat f(x)=\sum\limits_{i=0}^{n-1}\omega_{2n}^{i^2}x^i\sum\limits_{j=0}^{n-1}\omega_{2n}^{j^2}f_j\omega_{2n}^{-(i-j)^2}$$
用 $A(x)=\sum\limits_{i=0}^{n-1}\omega_{2n}^{i^2}f_i$ 和 $B(x)=\sum\limits_{i=0}^{n-1}\omega_{2n}^{-i^2}$ 卷一块即可做到DFT。注意这里的 $B$ 需要倍长,因为 $i-j$ 可以为正或负。
考虑IDFT,式子就变成
$$f(x)=\dfrac 1n\sum\limits_{i=0}^{n-1}x^i\sum\limits_{j=0}^{n-1}\hat f_j\omega_n^{-ij}$$
也可以类似处理。
这就是循环卷积的 `Bluestein` 算法。
由百度翻译,`stein` 有啤酒杯的意思,所以有时本人也会用“蓝酒杯”来称呼这个算法(但是这个称呼不怎么流行)。
## IV.I.[[POJ2821]TN's Kingdom III - Assassination](http://poj.org/problem?id=2821)
题意比较晦涩,反正就是已知多项式 $\alpha,\beta$ 循环卷积卷成了 $\gamma$。现在已知 $\beta,\gamma$,要你倒推出 $\alpha$。
因为 $n$ 次多项式与 $n$ 个点值在循环卷积意义下一一对应,所以循环卷积不需要烦恼甚么求逆之类,只需要对 $\beta,\gamma$ DFT,除一下得出 $\alpha$ 的点值,然后IDFT回来即可。
不知道SPJ出了甚么锅,输出 `double` 时写 `"%.4lf\n"` 死活过不去,改成 `"%.4f\n"` 就过了,不知道咋回事。
代码:
```cpp
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const double pi=acos(-1);
const int N=(1<<19)+100;
int rev[N],lim=1,LG,n;
struct cp{
double x,y;
cp(){x=y=0;}
cp(double X,double Y){x=X,y=Y;}
friend cp operator+(const cp&u,const cp&v){return cp(u.x+v.x,u.y+v.y);}
friend cp operator-(const cp&u,const cp&v){return cp(u.x-v.x,u.y-v.y);}
friend cp operator*(const cp&u,const double&v){return cp(u.x*v,u.y*v);}
friend cp operator/(const cp&u,const double&v){return cp(u.x/v,u.y/v);}
friend cp operator*(const cp&u,const cp&v){return cp(u.x*v.x-u.y*v.y,u.x*v.y+u.y*v.x);}
friend cp operator/(const cp&u,const cp&v){return cp((u.x*v.x+u.y*v.y)/(v.x*v.x+v.y*v.y),(u.y*v.x-u.x*v.y)/(v.x*v.x+v.y*v.y));}
void print(){printf("(%lf,%lf)",x,y);}
}A[N],B[N],G[N],H[N];
void FFT(cp*a,int tp){
for(int i=0;i<lim;i++)if(i>rev[i])swap(a[i],a[rev[i]]);
for(int md=1;md<lim;md<<=1){
cp rt(cos(pi/md),tp*sin(pi/md));
for(int stp=md<<1,pos=0;pos<lim;pos+=stp){
cp w(1,0);
for(int i=0;i<md;i++,w=w*rt){
cp x=a[pos+i],y=w*a[pos+md+i];
a[pos+i]=x+y;
a[pos+md+i]=x-y;
}
}
}
if(tp==-1)for(int i=0;i<lim;i++)a[i]=a[i]/lim;
}
void Bluestein(cp*a,int tp){
for(int i=0;i<lim;i++)A[i]=B[i]=cp(0,0);
for(int i=0;i<n;i++)A[i]=cp(cos(pi*i*i/n),tp*sin(pi*i*i/n))*a[i];
for(int i=0;i<(n<<1);i++)B[i]=cp(cos(pi*(i-n)*(i-n)/n),-tp*sin(pi*(i-n)*(i-n)/n));
FFT(A,1),FFT(B,1);
for(int i=0;i<lim;i++)A[i]=A[i]*B[i];
FFT(A,-1);
for(int i=0;i<n;i++)a[i]=A[i+n]*cp(cos(pi*i*i/n),tp*sin(pi*i*i/n));
if(tp==-1)for(int i=0;i<n;i++)a[i]=a[i]/n;
}
int main(){
scanf("%d",&n);
while(lim<(n<<2))LG++,lim<<=1;
for(int i=0;i<lim;i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<(LG-1));
for(int i=0;i<n;i++)scanf("%lf",&G[i].x);
for(int i=0;i<n;i++)scanf("%lf",&H[i].x);
// for(int i=0;i<n;i++)G[i].print();puts("");
// for(int i=0;i<n;i++)H[i].print();puts("");
Bluestein(G,1),Bluestein(H,1);
for(int i=0;i<n;i++)G[i]=H[i]/G[i];
Bluestein(G,-1);
for(int i=0;i<n;i++)printf("%.4f\n",G[i].x);
return 0;
}
```
## IV.II.[[HNOI2019]白兔之舞](https://www.luogu.com.cn/problem/P5293)
首先先列式子。
我们设乐曲长度是 $i$,则我们从 $L$ 层当中任选 $i$ 层停留即可。这个共有 $\dbinom Li$ 种方案。
然后再设转移矩阵是 $A$,我们便列出了式子为
$$[x,y]\sum\limits_{i=0}^Lx^{i\bmod k}\dbinom LiA^i$$
其中暂记 $[x,y]$ 表示矩阵 $[x,y]$ 位置的元素。老套路,单位根反演一下
$$[x,y]\dfrac1k\sum\limits_{i=0}^L\sum\limits_{t=0}^{k-1}x^t\sum\limits_{j=0}^{k-1}\omega_k^{(i-t)j}\dbinom LiA^i$$
调整顺序
$$[x,y]\dfrac1k\sum\limits_{t=0}^{k-1}x^t\sum\limits_{j=0}^{k-1}\omega_k^{-tj}\sum\limits_{i=0}^L\dbinom Li\omega_k^{ij}A^i$$
二项式展开
$$[x,y]\dfrac1k\sum\limits_{t=0}^{k-1}x^t\sum\limits_{j=0}^{k-1}\omega_k^{-tj}(\omega_k^jA+I)^L$$
这时我们发现全式中仅有 $(\omega_k^jA+I)^L$ 一项是矩阵,且该项结果仅与 $j$ 有关。于是我们设 $F_k=[x,y](\omega_k^jA+I)^L$。则上式变为
$$\dfrac1k\sum\limits_{t=0}^{k-1}x^t\sum\limits_{j=0}^{k-1}\omega_k^{-tj}F_j$$
我们翻出IDFT的式子
$$f(x)=\dfrac1n\sum\limits_{i=0}^{n-1}x^i\sum\limits_{j=0}^{n-1}\hat f_j\omega_n^{-ij}$$
发现两者长得惊人相似——实际上,对 $F_d$ 在长度为 $k$ 的意义下作循环IDFT,就能得到答案。
直接使用蓝酒杯即可。
需要注意的是,本题模数 $p$ 并不保证是NTT模数,故要使用MTT。
然后,$k$ 次单位根,就先求出 $p$ 的原根后,再求其 $\dfrac{p-1}k$ 次幂即可。
但是,需要注意的是!我们上文中提到的 `Bluestein` 的算式 $\dfrac{i^2+j^2-(i-j)^2}2=ij$ 中会在指数上出现 $\dfrac?2$,也即要对某个数开二次剩余,但是其可能不存在。
那咋办?
没关系,`Bluestein` 已经预见到了这种情形,并给了我们另一个算式:$\dbinom{i+j}2-\dbinom i2-\dbinom j2=ij$。
于是
$$\hat F(x)=\dfrac1k\sum\limits_{t=0}^{k-1}x^t\sum\limits_{j=0}^{k-1}\omega_k^{-tj}F_j=\dfrac1k\sum\limits_{t=0}^{k-1}x^t\sum\limits_{j=0}^{k-1}\omega_k^{\tbinom t2+\tbinom j2-\tbinom{t+j}2}F_j$$
然后就可以拆辣。
$$\hat F(x)=\dfrac1k\sum\limits_{t=0}^{k-1}x^t\omega_k^\tbinom t2\sum\limits_{j=0}^{k-1}\omega_k^{\tbinom j2}F_j\omega_k^{-\tbinom{t+j}2}$$
这样之后,设 $A_j=\omega_k^{\tbinom j2}F_j$,$B_j=\omega_k^{-\tbinom{2k-1-j}2}$。于是 $\hat F(x)=\dfrac1k\sum\limits_{t=0}^{k-1}x^t\omega_k^\tbinom t2\sum\limits_{j=0}^{k-1}A_jB_{2k-1-t-j}$。
然后就没有然后了。时间复杂度 $O(n\log n)$。
代码:
```cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1<<18;
namespace Poly{
const int G=3;
int lim,rev[N];
struct Polynomial{
int mod,invlim,A[N],B[N];
int ksm(int x,int y){int z=1;for(;y;y>>=1,x=1ll*x*x%mod)if(y&1)z=1ll*z*x%mod;return z;}
int NTT(int*a,int tp){
for(int i=0;i<lim;i++)if(i>rev[i])swap(a[i],a[rev[i]]);
for(int md=1;md<lim;md<<=1){
int rt=ksm(G,(mod-1)/(md<<1));
if(tp==-1)rt=ksm(rt,mod-2);
for(int stp=md<<1,pos=0;pos<lim;pos+=stp)for(int i=0,w=1;i<md;i++,w=1ll*w*rt%mod){
int x=a[pos+i],y=1ll*w*a[pos+md+i]%mod;
a[pos+i]=(x+y)%mod;
a[pos+md+i]=(x+mod-y)%mod;
}
}
if(tp==-1)for(int i=0;i<lim;i++)a[i]=1ll*a[i]*invlim%mod;
}
void mul(int*a,int*b){
invlim=ksm(lim,mod-2);
for(int i=0;i<lim;i++)A[i]=B[i]=0;
for(int i=0;i<(lim>>1);i++)A[i]=a[i],B[i]=b[i];
NTT(A,1),NTT(B,1);
for(int i=0;i<lim;i++)A[i]=1ll*A[i]*B[i]%mod;
NTT(A,-1);
}
}P[3];
int ksm(int x,int y,int mod){int z=1;for(;y;y>>=1,x=1ll*x*x%mod)if(y&1)z=1ll*z*x%mod;return z;}
int mod0=P[0].mod=998244353,mod1=P[1].mod=469762049,mod2=P[2].mod=1004535809;
const int inv0_1=ksm(mod0,mod1-2,mod1);
const int inv01_2=ksm(1ll*mod0*mod1%mod2,mod2-2,mod2);
int mod,phi;
void mul(int*a,int*b,int*c,int LG){
lim=1<<LG;for(int i=0;i<lim;i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<(LG-1));
// printf("%d:\n",lim);
// for(int i=0;i<lim;i++)printf("%d ",a[i]);puts("");
// for(int i=0;i<lim;i++)printf("%d ",b[i]);puts("");
for(int i=0;i<3;i++)P[i].mul(a,b);
// for(int k=0;k<3;k++){for(int i=0;i<lim;i++)printf("%d ",P[k].A[i]);puts("");}puts("");
for(int i=0;i<lim;i++){
ll tmp=(0ll+P[0].A[i]+1ll*(P[1].A[i]-P[0].A[i]%mod1+mod1)%mod1*inv0_1%mod1*mod0)%(1ll*mod0*mod1);
c[i]=(tmp%mod+1ll*(P[2].A[i]-tmp%mod2+mod2)*inv01_2%mod2*mod0%mod*mod1%mod)%mod;
}
}
}
using namespace Poly;
int K,omega,F[N],A[N],B[N];
namespace Initialization{
int n,L,x,y;
struct Matrix{
int a[3][3];
Matrix(){memset(a,0,sizeof(a));}
int*operator[](const int&x){return a[x];}
friend Matrix operator*(Matrix&u,Matrix&v){
Matrix w;
for(int i=0;i<n;i++)for(int j=0;j<n;j++)for(int k=0;k<n;k++)(w[i][j]+=1ll*u[i][k]*v[k][j]%mod)%=mod;
return w;
}
}W,M;
int KSM(int T){
Matrix R;
for(int i=0;i<n;i++)R[i][i]=1;
for(;T;T>>=1,M=M*M)if(T&1)R=R*M;
return R[x][y];
}
vector<int>v;
int G;
void init(){
scanf("%d%d%d%d%d%d",&n,&K,&L,&x,&y,&mod),x--,y--;
for(int i=0;i<n;i++)for(int j=0;j<n;j++)scanf("%d",&W[i][j]);
phi=mod-1;
int tmp=phi;
for(int i=2;i*i<=tmp;i++){
if(tmp%i)continue;
v.push_back(i);
while(!(tmp%i))tmp/=i;
}
if(tmp!=1)v.push_back(tmp);
for(G=1;;G++){
bool ok=true;
for(auto i:v)if(ksm(G,phi/i,mod)==1){ok=false;break;}
if(ok)break;
}
omega=ksm(G,phi/K,mod);
for(int i=0,w=1;i<K;i++,w=1ll*w*omega%mod){
for(int j=0;j<n;j++)for(int k=0;k<n;k++)M[j][k]=1ll*W[j][k]*w%mod;
for(int j=0;j<n;j++)(++M[j][j])%=mod;
F[i]=KSM(L);
}
}
}
int binom(int x){return (1ll*x*(x-1)/2)%K;}
int main(){
Initialization::init();
int LG=0;while((1<<LG)<(K<<2))LG++;
for(int i=0;i<K;i++)A[i]=1ll*F[i]*ksm(omega,binom(i),mod)%mod;
for(int i=0;i<2*K;i++)B[i]=ksm(omega,K-binom(2*K-1-i),mod);
// for(int i=0;i<K;i++)printf("%d ",A[i]);puts("");
// for(int i=0;i<2*K;i++)printf("%d ",B[i]);puts("");
mul(A,B,F,LG);
int invk=ksm(K,mod-2,mod);
for(int i=0;i<K;i++)printf("%d\n",1ll*invk*F[2*K-1-i]%mod*ksm(omega,binom(i),mod)%mod);
return 0;
}
```