学习内容:国家集训队2016论文 - 再谈快速傅里叶变换

模板题:http://uoj.ac/problem/34

1.基本介绍

对长度为L的\(A(x),B(x)\)进行DFT,可以利用

\[\begin{align}
P(x)=A(x)+iB(x) \tag{1} \\
Q(x)=A(x)-iB(x) \tag{2}
\end{align}
\]

对\(P(x)\)进行DFT,得到\(F_p\)。

\(Q(x)\)的结果 DFT\(F_q[k]=!(F_p[2L-k])\),(!表示取共轭)(证明见论文)。

\[\begin{align}
DFT(A[k])=\frac{F_p[k]+F_q[k]} 2 \tag{3} \\
DFT(B[k])=-i\frac{F_p[k]-F_q[k]} 2 \tag{4}
\end{align}
\]

这就是两两合并计算DFT的方法,2次DFT优化为了1次。

IDFT的计算有两种方法,一种是带入\(-w_n^k\),另一种是将序列[1..n-1]翻转,再进行FFT,两种方法结果都要除以n。

//495ms
#include <bits/stdc++.h>
#define rep(i,l,r) for(int i=l,ed=r;i<ed;++i)
typedef long long ll;
const double PI = acos(-1);
const int N = 1<<20;
const int BUF_SIZE=33554431;
using namespace std; struct buf{
char a[BUF_SIZE],b[BUF_SIZE],*s,*t;
buf():s(a),t(b){a[fread(a,1,sizeof a,stdin)]=0;}
~buf(){fwrite(b,1,t-b,stdout);}
operator int(){
int x=0;
while(*s<48)++s;
while(*s>32)
x=x*10+*s++-48;
return x;
}
void out(int x){
static char c[12];
char*i=c;
if(!x)*t++=48;
else{
while(x){
int y=x/10;
*i++=x-y*10+48,x=y;
}
while(i!=c)*t++=*--i;
}
*t++=10;
}
}it;
struct cp{
double x,y;
cp(double _x=0,double _y=0):x(_x),y(_y){}
cp operator +(const cp&amp; b)const{return cp(x+b.x,y+b.y);}
cp operator -(const cp&amp; b)const{return cp(x-b.x,y-b.y);}
cp operator *(const cp&amp; b)const{return cp(x*b.x-y*b.y,x*b.y+y*b.x);}
cp operator !()const{return cp(x,-y);}
}w[N];
void fft(cp p[],int n){
for(int i=0,j=0;i<n;++i){
if(i>j)swap(p[i],p[j]);
for(int l=n>>1;(j^=l)<l;l>>=1);
}
for(int i=2;i<=n;i<<=1)
for(int j=0,m=i>>1;j<n;j+=i)
rep(k,0,m){
cp b=w[n/i*k]*p[j+m+k];
p[j+m+k]=p[j+k]-b;
p[j+k]=p[j+k]+b;
}
}
void conv(int n,ll *x,ll *y,ll *z){
static cp p[N],q[N],h(0,-0.25);
rep(i,0,n){
w[i]=cp(cos(2*PI*i/n),sin(2*PI*i/n));
p[i]=cp(x[i],y[i]);
}
fft(p,n);
rep(i,0,n){
int j=i?(n-i):0;
q[j]=(p[i]*p[i]-!p[j]*!p[j])*h;
}
fft(q,n);
rep(i,0,n)z[i]=q[i].x/n+0.5;
}
int n,m,p;
ll a[N],b[N],c[N];
int main(){
n=it+1;m=it+1;
rep(i,0,n) a[i]=it;
rep(i,0,m) b[i]=it;
for(n+=m-1,p=1;p<n;p<<=1);
conv(p,a,b,c);
rep(i,0,n)it.out(c[i]);
return 0;
}

2.更快的卷积

将\(A(x)\)表示为\(A_0(x^2)+xA_1(x^2)\),\(、A_0(x^2)、xA_1(x^2)\)分别是偶次项、奇次项的和。

那么

\[\begin{align}
A(x)B(x)&=(A_0(x^2)+xA_1(x^2))(B_0(x^2)+xB_1(x^2))\\
&=A_0(x^2)B_0(x^2)+x(A_0(x^2)B_1(x^2)+A_1(x^2)B_0(x^2))+x^2A_1(x^2)B_1(x^2)
\end{align}
\]

可以分别对\(A_0(x)、A_1(x)、B_0(x)、B_1(x)\)计算DFT,然后再把上式\(x^0,x^1,x^2\)的系数算出来,再进行3次IDFT。共7次。

DFT可以两两合并优化为2次,且是两次长度为L(原来是2L)的DFT。

IDFT时也可以两两合并,于是就需要2次长度L的IDFT。共4次。

如果这两次IDFT还可以两两合并,那就只要计算一次IDFT。共3次长度L的计算。

推导如下:

\(A_0(x^2)B_1(x^2)+A_1(x^2)B_0(x^2)\)的 IDFT 结果就是奇数项的系数。\(A_0(x^2)B_0(x^2)\)和\(x^2A_1(x^2)B_1(x^2)\) 则是偶数项的系数。

将\(A_0(x^2)B_0(x^2)\)和\(x^2A_1(x^2)B_1(x^2)\)看做是关于\(x^2\)的多项式,可以两两合并计算。令

\[g=DFT(A_0)\cdot DFT(B_0)+w[k]DFT(A_1)\cdot DFT(B_1)\\
f=DFT(A_0)\cdot DFT(B_1)+DFT(A_1)\cdot DFT(B_0)
\]

\(xA(x)\)就是\(w_n^k\cdot DFT(A)\)。我们只要计算出\(IDFT(g)\)和\(IDFT(f)\)即可。

如果 IDFT 的结果是实数,那么两个 IDFT 就可以合并计算,令

\[P(x)=g+i\cdot f
\]

那么

\[IDFT(P(x))=IDFT(f)+i \cdot IDFT(g)
\]

于是取实部和虚部分别作为奇数和偶数项的系数即可。

\[j=\begin{cases}
0& k=0\\
n-k& k\neq 0
\end{cases}
\]

那么

\[\begin{aligned}
g&=\frac {P_k+!P_j}{2}\cdot \frac {Q_k+!Q_j}{2}+w[k]\cdot \frac {P_k-!P_j}{-2i}\cdot \frac {Q_k-!Q_j}{-2i}\\
&=\frac 1 4 [(P_k+!P_j)\cdot(Q_k+!Q_j)-w[k]\cdot(P_k-!P_j)\cdot(Q_k-!Q_j)]\\
\\
f&=\frac {P_k+!P_j} 2 \cdot \frac{Q_k-!Q_j}{-2}i+\frac {Q_k+!Q_j} 2 \cdot \frac{P_k-!P_j}{-2}i\\
&=\frac i{-4}[2\cdot P_k\cdot Q_k-2\cdot !P_j\cdot !Q_j]
\end{aligned}
\]

于是

\[\begin{aligned}
g+f\cdot i&=\frac 1 4 [(P_k+!P_j)\cdot(Q_k+!Q_j)-w[k]\cdot(P_k-!P_j)\cdot(Q_k-!Q_j)-2\cdot P_k\cdot Q_k+2 !(P_j\cdot Q_j)]\\
&=\frac 1 4 [-(P_k-!P_j)\cdot(Q_k-!Q_j)+2\cdot (P_k\cdot Q_k+!(P_j\cdot Q_j))\\
&-w[k]\cdot(P_k-!P_j)\cdot(Q_k-!Q_j)+2\cdot P_k\cdot Q_k-2\cdot !(P_j\cdot Q_j)]\\
&=Q_k\cdot P_k-\frac 1 4[(1+w[k])\cdot (P_k-!P_j)\cdot(Q_k-!Q_j)]\\
\end{aligned}
\]

//325ms
#include <bits/stdc++.h>
#define rep(i,l,r) for(int i=l,ed=r;i<ed;++i)
typedef long long ll;
const double PI = acos(-1);
const int N = 1<<20;
const int BUF_SIZE=33554431;
using namespace std; struct buf{
char a[BUF_SIZE],b[BUF_SIZE],*s,*t;
buf():s(a),t(b){a[fread(a,1,sizeof a,stdin)]=0;}
~buf(){fwrite(b,1,t-b,stdout);}
operator int(){
int x=0;
while(*s<48)++s;
while(*s>32)
x=x*10+*s++-48;
return x;
}
void out(int x){
static char c[12];
char*i=c;
if(!x)*t++=48;
else{
while(x){
int y=x/10;
*i++=x-y*10+48,x=y;
}
while(i!=c)*t++=*--i;
}
*t++=10;
}
}it;
struct cp{
double x,y;
cp(double _x=0,double _y=0):x(_x),y(_y){}
cp operator +(const cp&amp; b)const{return cp(x+b.x,y+b.y);}
cp operator -(const cp&amp; b)const{return cp(x-b.x,y-b.y);}
cp operator *(const cp&amp; b)const{return cp(x*b.x-y*b.y,x*b.y+y*b.x);}
cp operator *(double b)const{return cp(b*x,b*y);}
cp operator !()const{return cp(x,-y);}
}w[N];
void fft(cp *p,int n){
for(int i=0,j=0;i<n;++i){
if(i>j)swap(p[i],p[j]);
for(int l=n>>1;(j^=l)<l;l>>=1);
}
for(int i=2;i<=n;i<<=1)
for(int j=0,m=i>>1;j<n;j+=i)
rep(k,0,m){
cp b=w[n/i*k]*p[j+m+k];
p[j+m+k]=p[j+k]-b;
p[j+k]=p[j+k]+b;
}
}
void conv(int n,ll *x,ll *y,ll *z){
static cp p[N],q[N],a[N];
rep(i,0,n){
(i&amp;1?p[i>>1].y:p[i>>1].x)=x[i];
(i&amp;1?q[i>>1].y:q[i>>1].x)=y[i];
}
rep(i,0,n>>=1)w[i]=cp(cos(2*PI*i/n),sin(2*PI*i/n));
fft(p,n);fft(q,n);
rep(i,0,n){
int j=i?n-i:0;
a[j]=p[i]*q[i]-((cp(1,0)+w[i])*(p[i]-!p[j])*(q[i]-!q[j]))*0.25;
}
fft(a,n);
rep(i,0,n)z[i<<1]=a[i].x/n+0.5,z[i<<1|1]=a[i].y/n+0.5;
}
int n,m,p;
ll a[N],b[N],c[N];
int main(){
n=it+1;m=it+1;
rep(i,0,n) a[i]=it;
rep(i,0,m) b[i]=it;
for(n+=m-1,p=2;p<n;p<<=1);
conv(p,a,b,c);
rep(i,0,n)it.out(c[i]);
return 0;
}

3.拆系数FFT

要计算任意模数的卷积,我们一般考虑NTT+中国剩余定理CRT。NTT中需要模数是质数且表示为\(p=c\cdot 2^k+1\)中\(2^k\)要不小于n。

考虑直接算出卷积不取模,那么每个数不会超过\(M^2n\)。假设模数\(M\)是\(10^9\)级别,n是\(10^5\)级别,那么结果都是\(10^{23}\)级别,我们可以找三个都是\(10^9\)级别满足NTT要求的模数,利用中国剩余定理就能得到在\(10^{27}\)级别的模数意义下的结果,再对\(M\)取模即可。

但是这样常数就要乘3了。效率太低。拆系数FFT就是替代NTT解决模任意数且非常高效的算法。

如果利用FFT计算,浮点数会有误差,int128是一个方法,但是不是所有场合都能使用。所以需要拆系数。

设\(M_0=\lceil \sqrt M\rceil\),设

\[a_i=k[a_i]M_0+b[a_i]\\
b_i=k[b_i]M_0+b[b_i]
\]

其中\(k[a_i],b[a_i]< M_0\)。

假设\(K_a(x)\)是以\(k[a_i]\)为系数的多项式,\(B_a(x)\)是以\(b[a_i]\)为系数的多项式,\(K_b(x),B_b(x)\)同理,则:

\[A(x)=K_a(x)M_0+B_a(x)\\
B(x)=K_b(x)M_0+B_b(x)\\
A(x)B(x)=K_a(x)K_b(x)M_0^2+(K_a(x)B_b(x)+K_b(x)B_a(x))M_0+B_a(x)B_b(x)
\]

和上面「更快的卷积」一样分析,两两合并可以将7次DFT及IDFT计算优化为4次:

\(M_0\)可以取一个超过\(\sqrt M\)的2的幂次,比较方便计算。

\[P(x)=K_a(x)+iB_a(x)\\
Q(x)=K_b(x)+iB_b(x)
\]

可知

\[DFT(K_a[k])=\frac {F_p[k]+!(F_p[(n-k)\%n])} 2\\
DFT(B_a[k])=-i\frac {F_p[k]-!(F_p[(n-k)\%n])} 2\\
DFT(K_b[k])=\frac {F_q[k]-!(F_q[(n-k)\%n])} 2\\
DFT(B_b[k])=-i\frac {F_q[k]-!(F_q[(n-k)\%n])} 2\\
\]

于是只要计算出P(x)的DFT:\(F_p(x)\)和Q(x)的DFT:\(F_q(x)\),就能求出\(K_a(x),B_a(x),K_b(x),B_b(x)\)的DFT。

接下来IDFT的两两合并,以\(K_a(x)K_b(x)\)和\(K_a(x)B_b(x)\)为例,令

\[dfta[k]=DFT(K_a[k])\cdot DFT(K_b[k])\\
dftb[k]=DFT(K_a[k])\cdot DFT(B_b[k])
\]

我们需要对\(dfta(x)\)和\(dftb(x)\)进行IDFT。注意到这里IDFT的结果一定是实数,那么令

\[p[k]=dfta[k]+i\cdot dftb[k]
\]

那么 \(IDFT(p)\) 的实部除以n就是\(K_a(x)K_b(x)\),虚部除以n就是\(K_a(x)B_b(x)\)。

由于\(、k[x]、b[x]\)都是不超过\(2^{15}\)的数,于是就不容易被卡精度了。计算出来的结果再取模M就是答案了。

//933ms
#include <bits/stdc++.h>
#define rep(i,l,r) for(int i=l,ed=r;i<ed;++i)
typedef long long ll;
const double PI = acos(-1);
const int N = 1<<20;
const ll mod = 1e9+7;
const int BUF_SIZE=33554431;
using namespace std; struct buf{
char a[BUF_SIZE],b[BUF_SIZE],*s,*t;
buf():s(a),t(b){a[fread(a,1,sizeof a,stdin)]=0;}
~buf(){fwrite(b,1,t-b,stdout);}
operator int(){
int x=0;
while(*s<48)++s;
while(*s>32)
x=x*10+*s++-48;
return x;
}
void out(int x){
static char c[12];
char*i=c;
if(!x)*t++=48;
else{
while(x){
int y=x/10;
*i++=x-y*10+48,x=y;
}
while(i!=c)*t++=*--i;
}
*t++=10;
}
}it;
struct cp{
double x,y;
cp(double _x=0,double _y=0):x(_x),y(_y){}
cp operator +(const cp&amp; b)const{return cp(x+b.x,y+b.y);}
cp operator -(const cp&amp; b)const{return cp(x-b.x,y-b.y);}
cp operator *(const cp&amp; b)const{return cp(x*b.x-y*b.y,x*b.y+y*b.x);}
cp operator !()const{return cp(x,-y);}
}w[N];
void fft(cp p[],int n){
for(int i=0,j=0;i<n;++i){
if(i>j)swap(p[i],p[j]);
for(int l=n>>1;(j^=l)<l;l>>=1);
}
for(int i=2;i<=n;i<<=1)
for(int j=0,m=i>>1;j<n;j+=i)
rep(k,0,m){
cp b=w[n/i*k]*p[j+m+k];
p[j+m+k]=p[j+k]-b;
p[j+k]=p[j+k]+b;
}
}
void conv(int n,ll *x,ll *y,ll *z){
static cp p[N],q[N],a[N],b[N],c[N],d[N];
static cp r(0.5,0),h(0,-0.5),o(0,1);
rep(i,0,n){
w[i]=cp(cos(2*PI*i/n),sin(2*PI*i/n));
x[i]=(x[i]+mod)%mod,y[i]=(y[i]+mod)%mod;
p[i]=cp(x[i]>>15,x[i]&amp;32767),q[i]=cp(y[i]>>15,y[i]&amp;32767);
}
fft(p,n);fft(q,n);
rep(i,0,n){
int j=i?(n-i):0;
static cp ka,ba,kb,bb;
ka=(p[i]+!p[j])*r;
ba=(p[i]-!p[j])*h;
kb=(q[i]+!q[j])*r;
bb=(q[i]-!q[j])*h;
a[j]=ka*kb;b[j]=ka*bb;
c[j]=kb*ba;d[j]=ba*bb;
}
rep(i,0,n){
p[i]=a[i]+b[i]*o;
q[i]=c[i]+d[i]*o;
}
fft(p,n);fft(q,n);
rep(i,0,n){
ll a,b,c,d;
a=(ll)(p[i].x/n+0.5)%mod;
b=(ll)(p[i].y/n+0.5)%mod;
c=(ll)(q[i].x/n+0.5)%mod;
d=(ll)(q[i].y/n+0.5)%mod;
z[i]=((a<<30)+((b+c)<<15)+d)%mod;
}
}
int n,m,p;
ll a[N],b[N],c[N];
int main(){
n=it+1;m=it+1;
rep(i,0,n) a[i]=it;
rep(i,0,m) b[i]=it;
for(n+=m-1,p=1;p<n;p<<=1);
conv(p,a,b,c);
rep(i,0,n)it.out((c[i]+mod)%mod);
return 0;
}

题目:

待补充

拆系数FFT的更多相关文章

  1. 拆系数FFT及其部分优化

    模拟考某题一开始由于校内OJ太慢直接拆系数FFT跑不过 后来被神仙婊了一顿之后发现复杂度写炸了改了改随便过 模版题:任意模数NTT 三模数NTT 常数巨大,跑的极慢 拆系数FFT 原理是对于两个多项式 ...

  2. 拆系数FFT(任意模数FFT)

    拆系数FFT 对于任意模数 \(mod\) 设\(m=\sqrt {mod}\) 把多项式\(A(x)\)和\(B(x)\)的系数都拆成\(a\times m+b\)的形式,时\(a, b\)都小于\ ...

  3. hdu 5730 Shell Necklace——多项式求逆+拆系数FFT

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=5730 可以用分治FFT.但自己只写了多项式求逆. 和COGS2259几乎很像.设A(x),指数是长度,系数 ...

  4. 洛谷 4245 【模板】任意模数NTT——三模数NTT / 拆系数FFT

    题目:https://www.luogu.org/problemnew/show/P4245 三模数NTT: 大概是用3个模数分别做一遍,用中国剩余定理合并. 前两个合并起来变成一个 long lon ...

  5. 洛谷 P4245 [模板]任意模数NTT —— 三模数NTT / 拆系数FFT(MTT)

    题目:https://www.luogu.org/problemnew/show/P4245 用三模数NTT做,需要注意时间和细节: 注意各种地方要取模!传入 upt() 里面的数一定要不超过2倍 m ...

  6. hdu6088 组合数+反演+拆系数fft

    题意:两个人van石头剪子布的游戏一共n盘,假设A赢了a盘,B赢了b盘,那么得分是gcd(a,b),求得分的期望*\(3^{2*n}\) 题解:根据题意很明显有\(ans=3^{n}*\sum_{a= ...

  7. 拆系数$FFT$($4$遍$DFT$)

    #include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> ...

  8. 51NOD 1258 序列求和 V4 [任意模数fft 多项式求逆元 伯努利数]

    1258 序列求和 V4 题意:求\(S_m(n) = \sum_{i=1}^n i^m \mod 10^9+7\),多组数据,\(T \le 500, n \le 10^{18}, k \le 50 ...

  9. hdu 4656 Evaluation [任意模数fft trick]

    hdu 4656 Evaluation 题意:给出\(n,b,c,d,f(x) = \sum_{i=1}^{n-1} a_ix^i\),求\(f(b\cdot c^{2k}+d):0\le k < ...

随机推荐

  1. 关于C#chart图表实现多条折线动态绑定数据的问题

    之前就已经实现了多条折线绑定数据并显示,但不是动态绑定,而是每一条数据都要进行一次绑定,个人觉得在解决实际问题时,这样的解决方法过于笨重且缺乏扩展性,这次主要是对代码进行优化,实现写一遍代码,无论数据 ...

  2. 设计模式系列13:模板方法模式(Template Method Pattern)

    定义 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中.模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤.    --<设计模式GoF> UML类图 使用场景 有 ...

  3. Android开发支付集成——支付宝集成

    微信支付传送门:https://www.cnblogs.com/dingxiansen/p/9209159.html 一.支付宝支付 1. 支付宝支付流程图 2. 集成前准备 去蚂蚁金服注册应用获取a ...

  4. Android Studio教程09-加载器Loader的使用

    目录 1.加载器特征 2. Loader API 3. 在应用中使用Loader 3.1. 启动加载器 3.2. 重启加载器 3.3. 使用LoaderManager回调 4. 实例: 访问用户联系人 ...

  5. (办公)springboot配置全局异常

    项目用到了springboot,本来很高兴,但是项目里什么东西都没有,验证,全局异常这些都需要自己区配置.最近springboot用的还是蛮多的,我还是做事情,把经验发表一下.全局统一的异常,首先异常 ...

  6. C#基础委托回顾

    C#基础委托回顾 前言 快忘记了. 委托的特点 委托类似于 C++ 函数指针,但它们是类型安全的. 委托允许将方法作为参数进行传递. 委托可用于定义回调方法. 委托可以链接在一起:例如,可以对一个事件 ...

  7. HTML语义化的理解

    语义化的主要目的:用正确的标签做正确的事情. 语义化验证方法:css裸奔--去掉css样式,然后看页面是否还具有很好的可读性. 语义化意义 / 优点: 1.让页面的内容结构化 2.利于浏览器解析和SE ...

  8. Swift 访问控制

    1.private private访问级别所修饰的属性或者方法只能在当前类里访问. 2.fileprivate fileprivate访问级别所修饰的属性或者方法在当前的Swift源文件里可以访问. ...

  9. zabbix配置邮件告警

    1.安装邮件服务 yum -y install mailx @qq.com #发邮件测试 2.添加报警媒介 输入接收告警邮件的邮箱 3.配置SMTP服务端 使用本机作为SMTP服务器 4.创建触发器 ...

  10. java 向上向下取整

    Math.floor(1.4)=1.0 Math.round(1.4)=1 Math.ceil(1.4)=2.0 Math.floor(1.5)=1.0 Math.round(1.5)=2 Math. ...