多项式全家桶
实现卷积的算法
First.FFT
两个多项式做乘法,暴力的想法是系数依次考虑,复杂度
然而如果我们观察出来了一个性质,不妨设我们现在想求解:
那么显然有乘出来的多项式
这是一个加法卷积。
所以
接下来是
将两个多项式转成点值表达。
将点值两两乘起来。
将点值换成系数表达。
前面那一部分我们叫做
前方高能:
当我们在做求点值表达的时候,会发现复杂度很高,原因是因为我们没有代入合适的值。
所以我们考虑代入合适的值进去。
单位根
他是定义在复平面的单位圆上的。
显然
以及
复数乘法有个好玩的,就是它的乘法遵循模长相乘幅角相加。
因为单位根定义在单位圆上所以模长是
我们尝试给一个
会由于下列两条性质而发生翻天覆地的变化:
且有:
第二:
原因很简单
这里我们定义的负数的关于原点对称了的。
且因为:
所以
若
于是有:
稍微化简一下得:
显然有分母不为
于是我们得到了求和性质。
前方真高能!
我们有了折半性质后怎么做
对于一个多项式
不妨设它为
不妨设:
于是有:
接下来我们代入
得到:
于是:
这里假设
所以我们想要求出
接下来我们考虑
不妨设它为
于是有:
得到:
即:
你会惊人的发现这个东东和前面的一样只是一个符号不同而已
这同时表明我们只要知道
于是复杂度就是
即
接下来我们考虑插值,即知道
这里设我们得到的点值为
我们发现实际上我们要做的就是求解这样的一组方程组:
我们可以将之写作矩阵的形式。
于是我们就只要对左边的矩阵求逆了。
假设现在的式子是:
那么我们需要求出
这个矩阵可以构造出来,我们利用一下求和性质。
于是新矩阵的第
如果我们令
即
于是可以得到:
也就是:
由求和性质
当
否则其为
所以我们构造出来的
也就有:
我们考虑求解
因为
首先
所以有:
你惊人的发现它变成了我们想要求出当
于是就再做一遍
复杂度
Second.NTT
我们尝试利用整数
首先是阶的定义:(下面的等于
如果有
则称满足此条件最小的
若一个数
我们尝试使用原根代替单位根进行计算。
首先要有单位根的如下几条性质:
我们尝试证明原根亦满足如下三条性质。
证明:
若存在两者相等,不妨设
于是有:
又
故
与定义矛盾,得证。
那么
不难得到:
把两边拆开,代入
其次:
我们接下来考虑将之指数看作一个角度为
那么其乘法就是幅角不断累加。
于是可以得到:
这里就感性理解吧,我实在是不会证了,按照单位根的方法理解。
性质
求和性质利用了等比数列求和的定理,我们套用即可,因为是在
故
有了这三个性质,我们就能愉快的把原根代入单位根运算了。
然后因为
所以我们取的
比较常用的
且
Third. 非递归版FFT 和NTT
最后得到的序列应该是:
我们打个表,发现:
变成了:
其实就是二进制反转了
考虑求出这个东东
貌似有一个递推式,不妨记
于是有:
那么有我们将
偶数类似,不用补
这样就
然后因为这个关系双向,所以我们做
#include<bits/stdc++.h>
using namespace std;
#define rep( i, s, t ) for( register int i = s; i <= t; ++ i )
#define re register
#define LL long long
int read() {
char cc = getchar(); int cn = 0, flus = 1;
while(cc < '0' || cc > '9') { if( cc == '-' ) flus = -flus; cc = getchar(); }
while(cc >= '0' && cc <= '9') cn = cn * 10 + cc - '0', cc = getchar();
return cn * flus;
}
const int P = 998244353 ;
const int G = 3 ;
const int Gi = 332748118 ;
const int N = 4e6 + 5 ;
int n, m, R[N], L, limit ;
LL A[N], B[N] ;
LL fpow( LL x, int k ) {
LL ans = 1, base = x ;
while( k ) {
if( k & 1 ) ans = ans * base % P ;
k >>= 1, base *= base, base %= P ;
}
return ans ;
}
void NTT( LL *a, int type ) {
for( int i = 0; i < limit; ++ i )
if( i < R[i] ) swap( a[i], a[R[i]] ) ;
for( int k = 1; k < limit; k <<= 1 ) {
LL dg = fpow( ( type == 1 ) ? G : Gi, ( P - 1 ) / ( k << 1 ) ); //这里要注意
for( int i = 0; i < limit; i += ( k << 1 ) ) //注意*2
for( LL j = i, g = 1; j < i + k; ++ j, g = ( g * dg ) % P ) {
LL Nx = a[j], Ny = ( a[j + k] * g ) % P;
a[j] = ( Nx + Ny ) % P, a[j + k] = ( Nx - Ny + P ) % P ;
}
}
}
void init() {
while( limit <= n + m ) limit <<= 1, ++ L ;
for( int i = 0; i < limit; ++ i ) R[i] = ( R[i >> 1] >> 1 ) | ( ( i & 1 ) << ( L - 1 ) ) ;
}
signed main()
{
n = read(), m = read(), limit = 1 ;
rep( i, 0, n ) A[i] = read() ;
rep( i, 0, m ) B[i] = read() ;
init(), NTT( A, 1 ), NTT( B, 1 ) ;
rep( i, 0, limit ) A[i] = ( A[i] * B[i] ) % P ;
NTT( A, -1 ) ; int inv = fpow( limit, P - 2 ) ;
rep( i, 0, n + m ) printf("%lld ", ( A[i] * inv ) % P ) ;
return 0;
}
Fourth.MTT/ 任意模数FFT
貌似有很多做法,先记着一个我会的,最慢的,
将每一项的系数拆成
于是显然有:
我们就分别对
然后考虑合并
其在
变成
然后暴力展开,发现还要对
最后合并系数即可,
以后学了别的会慢慢补充
MTT
考虑对于两个多项式
构造
显然有,记
记录
所以我们只要
然后求出了
就有:
然后对
上述优化过程后得到的
接下来考虑优化
假设我们有
根据求和性质,我们只需要求出其在
不妨构造
令
同上可得:
貌似还有
这样大概可以将两次算点值做到一次同时
然后就可以将
我寻思大家为什么都用鱼写的那个奇怪的东西叫做三次变两次啊...真正的三次变两次不应该是这玩意儿吗...这是真正意义上同时
然后就要开始上全家桶了qwq
华丽的分割线
多项式全家桶
First. 多项式求逆
给定
首先显然那些什么
于是我们考虑递推
不妨设已知:
则显然:
故做差,得到:
平方得到:
于是有:
两边同时乘
于是有:
如此递推即可。
从
复杂度
关于证明:
于是
所以
Second. 【模板】分治 FFT
考虑构造一个函数
当然这样的
类似的构造一个
于是显然有:
然后就有:
因为我们只要求出
因为
Third. 【模板】多项式除法
给定
求出
注意到如下性质:
Sixth. 多项式\ln /多项式\exp(4\&5 为泰勒展开+牛顿迭代)
这个东西要前置芝士...
定义式:
关于求导运算的一些结论:
证明:
于是就有:
因为
所以就有:
原式
于是:
证明:
可以构造
咕咕咕...
注意到
所以:
一些比较常用的结论:
积分和求导为逆运算,定义式:
然后积分可加...(面积可加)
于是同理求导可加
然后积分可以直接乘以倍数/系数,反过来求导也可以qwq
Fourth. 泰勒展开
首先是一个函数的泰勒展开:
其中
然而在精度允许的条件下由于阶乘非常的大所以只需要把
然后由于我们可以随便选择一个
于是我们就可以将原式转化为
Firth. 牛顿迭代
用于求解
与二分不同的是牛顿迭代的求法是对一个函数求导,降次,求解降次后的
多项式牛顿迭代即求解满足
假设现在已经求出了:
考虑拓展到
因为我们有:
所以就有:
又
然后又由于理论上应该有:
所以那个奇怪的减法会导致这个函数没有最后
于是就发生了神奇的结论!
没有最后
所以
于是令人惊喜的是我们泰勒展开的第二项就是
所以我们居然只需要展开两项即可!!!woc
所以就得到了神奇的结论:
于是就有:
于是就可以倍增处理了
问题的关键主要是对
下面给出两个对于
Sixth. 多项式\ln /多项式\exp
1. 多项式\ln
然后接下来推一下多项式
两边同时求导得:
令
又
所以得:
所以就只需要计算出
因为求导与积分互逆,所以求导可加个人感觉
首先有对一个数求导是
所以我们知道
然后在求一下
然后将
然后还原回去就套用上面求导那里的公式
2. 多项式\exp
现在想要求解:
于是两边同时取
可以设
这个多项式里面
我们假设有:
问题是求解
于是代入牛顿迭代的公式得到:
因为
而其中
所以我们可以得到:
然而因为求解
于是求解
即
不过常数比较大就是了......
Seventh. 多项式开根
与上面类似
假设有
两边同时平方得到:
可以设一个函数
代入牛顿迭代的式子就是:
然后类似的,将
所以得到:
于是要求逆,复杂度
#include<bits/stdc++.h>
using namespace std ;
#define rep( i, s, t ) for( register int i = s; i <= t; ++ i )
#define re register
#define LL long long
int gi() {
char cc = getchar() ; int cn = 0, flus = 1 ;
while( cc < '0' || cc > '9' ) { if( cc == '-' ) flus = - flus ; cc = getchar() ; }
while( cc >= '0' && cc <= '9' ) cn = cn * 10 + cc - '0', cc = getchar() ;
return cn * flus ;
}
const int P = 998244353 ;
const int Gi = 332748118 ;
const int G = 3 ;
const int N = 5e5 + 5 ;
int n, R[N], L, limit ;
LL A[N] ;
LL fpow( LL x, LL k ) {
LL ans = 1, base = x ;
while( k ) {
if( k & 1 ) ans = ( ans * base ) % P ;
base = ( 1ll * base * base ) % P, k >>= 1 ;
}
return ans % P ;
}
LL Gx[N], H[N], Inv ;
void Init( int x ) {
limit = 1, L = 0 ;
while( limit < x ) limit <<= 1, ++ L ; //因为都是2的整数次幂,实际上并不需要 <=
//这样会造成8倍长度的数组,最后TLE
rep( i, 0, limit - 1 ) R[i] = ( R[i >> 1] >> 1 ) | ( ( i & 1 ) << ( L - 1 ) ) ;
Inv = fpow( limit, P - 2 ) ;
}
void NTT( LL *a, int type ) {
rep( i, 0, limit - 1 ) if( R[i] > i ) swap( a[i], a[R[i]] ) ;
for( re int k = 1; k < limit; k <<= 1 ) {
int d = fpow( ( type == 1 ) ? G : Gi, ( P - 1 ) / ( k << 1 ) ) ;
for( re int i = 0; i < limit; i += ( k << 1 ) )
for( re LL j = i, g = 1; j < i + k; ++ j, g = ( d * g ) % P ) {
LL Nx = a[j], Ny = ( a[j + k] * g ) % P ;
a[j] = ( Nx + Ny ) % P, a[j + k] = ( Nx - Ny + P ) % P ;
}
}
if( type != 1 ) rep( i, 0, limit - 1 ) a[i] = ( a[i] * Inv ) % P ;
}
void GetInv( LL *a, int x ) {
int k = 1 ; memset( H, 0, sizeof(H) ), memset( Gx, 0, sizeof(Gx) ), H[0] = fpow( A[0], P - 2 ) ;
while( k < x ) { //这里同理
k <<= 1, Init( k * 2 ) ;
rep( i, 0, k - 1 ) Gx[i] = a[i] ;
NTT( Gx, 1 ), NTT( H, 1 ) ;
rep( i, 0, limit ) H[i] = ( 2ll - Gx[i] * H[i] % P + P ) * H[i] % P ;
NTT( H, -1 ) ; rep( i, k, limit ) H[i] = 0 ;
}
}
LL Gt[N], B[N] ;
void Sqrt( LL *a, int x ) {
int k = 1 ; LL Iv = 499122177 ; B[0] = 1 ;
while( k <= x ) {
k <<= 1 ; rep( i, 0, k - 1 ) Gt[i] = a[i] ;
GetInv( B, k ), Init( k * 2 ) ;
NTT( Gt, 1 ), NTT( H, 1 ), NTT( B, 1 ) ;
rep( i, 0, limit ) B[i] = 1ll * Iv * ( B[i] % P + Gt[i] * H[i] % P ) % P ;
NTT( B, -1 ) ; rep( i, k, limit ) B[i] = 0 ;
}
}
signed main()
{
n = gi() ;
rep( i, 0, n - 1 ) A[i] = gi() ;
Sqrt( A, n ) ;
rep( i, 0, n - 1 ) printf("%d ", B[i] ) ;
return 0 ;
}