多项式算法合集
文章目录
头函数
#define poly vector<int>
#define bg begin
#define pb push_back
const int mod=998244353,g=3;
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline void Add(int &a,int b){a=add(a,b);}
inline int dec(int a,int b){return a>=b?a-b:a-b+mod;}
inline void Dec(int &a,int b){a=dec(a,b);}
inline int mul(int a,int b){return 1ll*a*b>=mod?1ll*a*b%mod:a*b;}
inline void Mul(int &a,int b){a=mul(a,b);}
inline int ksm(int a,int b,int res=1){for(;b;b>>=1,a=mul(a,a))(b&1)&&(res=mul(res,a));return res;}
多项式加减点乘点除
幼儿园小朋友应该都会了吧
inline poly operator +(poly a,poly b){
poly c;int lim=max(a.size(),b.size());c.resize(lim);
for(int i=0;i<lim;i++)c[i]=add(a[i],b[i]);return c;
}
inline poly operator -(poly a,poly b){
poly c;int lim=max(a.size(),b.size());c.resize(lim);
for(int i=0;i<lim;i++)c[i]=dec(a[i],b[i]);return c;
}
inline poly operator *(poly a,int b){
for(int i=0;i<a.size();i++)Mul(a[i],b);return a;
}
inline poly operator /(poly a,int b){
for(int i=0,inv=ksm(b,mod-2);i<a.size();i++)Mul(a[i],inv);
return a;
}
多项式乘法
FFT:
前置
多项式的点值和系数表示法:
对于一个次多项式
被称作该多项式的系数表示
我们可以通过带任意一个都可以的到唯一的一个
但中一般一般都只是一个不定元,不会带入特定值计算,比如用作表示下标之类的
而如果我们把不同的带入进去得到的个点值叫做点值表示法
由个点也可以还原出唯一一个次多项式
虚数
即
考虑在平面直角坐标系内
将轴用来表示
复数更准确的定义是
这样平面上一个点就是的形式
而2个虚数相乘,就对应平面直角坐标系上2个向量模长相乘,极角相加
由于自带的复数很慢
所以我们一般手写复数结构体
const double pi=acos(-1);
struct complex{
double x,y;
complex(double _x=0,double _y=0):x(_x),y(_y){}
friend inline complex operator +(const complex &a,const complex &b){
return complex(a.x+b.x,a.y+b.y);
}
friend inline complex operator -(const complex &a,const complex &b){
return complex(a.x-b.x,a.y-b.y);
}
friend inline complex operator /(const complex &a,const double &b){
return complex(a.x/b,a.y/b);
}
friend inline complex operator *(const complex &a,const complex &b){
return complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);
}
};
单位根
的根本
次单位根指的是满足的复数
次单位根有个,分别表示为
实际上对应的将平面单位圆周上的个点
这些点将单位圆周均分成块,且构成一个正边形
更准确的表示为
即表示
即单位圆上的点
单位根几个重要的性质:
1、消去引理
对于任何整数
有
证明:
2、折半引理
如果且为偶数,那么
对所有次单位根平方,得到的集合是个次单位根的集合
说白了就是或者就是
证明:画个单位圆,就是旋转了,自然取反
3、求和引理
对于任意整数和
有
考虑这其实是一个等比数列,时
原式
而当时,原式显然为
这个性质在后面很重要
算法
考虑对于两个多项式
我们要求一个次多项式
更具体的满足
也就是2个数列倒着乘的和,所谓的卷积
考虑如果我们直接拆开暴力做是的
当然也有一种分治乘法可以做到(大概就是大常数)
而可以做到求出
下面为了方便假设,也没有区别
考虑如果我们有个值
如果已经求出
和
也就是分别求出的点值
那么我们可以直接在的时间内求出
现在我们考虑这样
先对,求出个点的点值,乘起来得到的点值
又对于一个次多项式,如果我们知道其个不同点值
就一定可以还原出一个唯一的多项式
于是最后再由点值表示还原出原来的
注意由于实际乘出来最高系数是
所以我们需要带入个点求值
于是我们会将补0到次项
实际上由于的特殊性
我们会将项数补充到2的整数次幂次
当然非二的整数次幂项的多项式乘法也是可以做的(见下面补充的混合基和)
第一步将系数转成点值是正变换,称作,单次复杂度
由点值还原系数为逆变换,称作,单次复杂度
于是总复杂度就是
整个操作被称为快速傅里叶变换
DFT:
考虑我们带入次单位根
考虑将下标按照奇偶分类
这样和都只有项了
可以继续递归去做
尽管现在复杂度并没有变化
考虑对于
考虑单位根的消去引理
则
我们发现唯一不同的就是第二项的符号
也就是说如果我们知道和
我们就可以同时知道和
考虑对于我们求
就只需要知道和
就可以在的时间内得到
而,系数都只有个,所以规模只有原来的一般
递归求解即可
时间复杂度
这里也是之所以要把项数补充到
因为每一次都把项分成项
如果是奇数,那就没法分开了
由于递归常数比较大,而一般有关的题时间瓶颈就在上面
不知道为什么 常数也很大
写的差的甚至可以跑的数据掉
所以我们考虑迭代做
由于每个数最终在的位置和原来不一样
所以我们要预处理出最终的位置上
据说找规律得到了预处理的方法
代码如下:
没看懂,选择全文背诵
当然也可以模拟最终位置
int rev[N<<2];
inline void init(int lim){
for(int i=0;i<lim;i++)rev[i]=(rev[i>>1]>>1)|((i&1)*(lim>>1));
}
我们先把每个数放到最终应该在的地方然后一步步迭代回去就是了
inline void DFT(complex f[],int lim){
for(int i=0;i<lim;i++)if(rev[i]>i)swap(f[i],f[rev[i]]);
for(int mid=1;mid<lim;mid<<=1){
complex now=plx(cos(pi/mid),sin(pi/mid));
for(int i=0;i<lim;i+=(mid<<1)){
complex w=plx(1,0);
for(int j=0;j<mid;j++,w=w*now){
plx p0=f[i+j],p1=w*f[i+j+mid];
f[i+j]=p0+p1,f[i+j+mid]=p0-p1;
}
}
}
}
IDFT:
以下摘抄自神犇(太难写了QAQ)
代码实现
const double pi=acos(-1);
struct plx{
double x,y;
plx(double _x=0,double _y=0):x(_x),y(_y){}
friend inline plx operator +(const plx &a,const plx &b){
return plx(a.x+b.x,a.y+b.y);
}
friend inline plx operator -(const plx &a,const plx &b){
return plx(a.x-b.x,a.y-b.y);
}
friend inline plx operator /(const plx &a,const double &b){
return plx(a.x/b,a.y/b);
}
friend inline plx operator *(const plx &a,const plx &b){
return plx(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);
}
};
inline void fft(plx f[],int lim,int kd){//kd表示在做正变换还是逆变换
for(int i=0;i<lim;i++)if(rev[i]>i)swap(f[i],f[rev[i]]);
for(int mid=1;mid<lim;mid<<=1){
plx now=plx(cos(pi/mid),kd*sin(pi/mid));
for(int i=0;i<lim;i+=(mid<<1)){
plx w=plx(1,0);
for(int j=0;j<mid;j++,w=w*now){
plx p0=f[i+j],p1=w*f[i+j+mid];
f[i+j]=p0+p1,f[i+j+mid]=p0-p1;
}
}
}
if(kd==-1)for(int i=0;i<lim;i++)f[i]=f[i]/lim;
}
NTT:
由于涉及复数和实数运算,实际会出现精度误差
在整数运算时难免会出锅
所以我们考虑一个能在模意义下的变换
这就是
首先引入原根的概念
对于一个素数,其原根定义为满足互不相同的数
又由于费马定理,对一个素数,有
这个和单位根很相似
我们考虑单位根之所以能够做,是因为的三个性质
考虑如果我们对于一个素数,我们令
这样我们就可以满足互不相同且满足这些性质(不想证了)
但这也就限制了的模数必须是的形式
否则就要做任意模数
做法就是选出几个模数分别做之后合并答案(并不会)
代码和类似
由于
所以我们可以先做的时候不管正逆变换
最后把~反转一下就可以了
代码实现
inline void ntt(poly &f,int lim,int kd){
for(int i=0;i<lim;i++)if(i<rev[i])swap(f[i],f[rev[i]]);
for(int mid=1;mid<lim;mid<<=1){
int now=ksm(g,(mod-1)/(mid<<1));
for(int i=0;i<lim;i+=(mid<<1)){
int w=1;
for(int j=0;j<mid;j++,w=mul(w,now)){
int a0=f[i+j],a1=mul(w,f[i+j+mid]);
f[i+j]=add(a0,a1),f[i+j+mid]=dec(a0,a1);
}
}
}
if(kd==-1&&(reverse(f.bg()+1,f.bg()+lim),1))for(int i=0,inv=ksm(lim,mod-2);i<lim;i++)f[i]=mul(f[i],inv);
}
由于中每次乘起来很耗费常数
我也不知道为什么,但就是很耗时间
于是我们可以预处理出原根优化常数
预处理原根
const int N=1000005,C=21;
int *w[22];
inline void init_w(){
for(int i=1;i<=C;i++)
w[i]=new int[1<<(i-1)];
int wn=ksm(g,(mod-1)/(1<<C));
w[C][0]=1;
for(int i=1;i<(1<<(C-1));i++)w[C][i]=mul(w[C][i-1],wn);
for(int i=C-1;i;i--)
for(int j=0;j<(1<<(i-1));j++)
w[i][j]=w[i+1][j<<1];
}
速度比不预处理快了差不多到不等
其实本身常数不算很大,运算常数大概也只有5、6左右
不过下标不连续可能会导致慢一些
inline void ntt(poly &f,int lim,int kd){
for(int i=0;i<lim;i++)if(i>rev[i])swap(f[i],f[rev[i]]);
for(int mid=1,l=1;mid<lim;mid<<=1,l++)
for(int i=0;i<lim;i+=(mid<<1))
for(int j=0,a0,a1;j<mid;j++)
a0=f[i+j],a1=mul(f[i+j+mid],w[l][j]),
f[i+j]=add(a0,a1),f[i+j+mid]=dec(a0,a1);
if(kd==-1&&(reverse(f.begin()+1,f.begin()+lim),1))
for(int inv=ksm(lim,mod-2),i=0;i<lim;i++)Mul(f[i],inv);
}
乘法
可以在比较小的时候暴力加循环展开 优化常数
inline poly operator *(poly a,poly b){
int deg=a.size()+b.size()-1,lim=1;
if(deg<=128){
poly c(deg,0);
for(int i=0;i<a.size();i++)
for(int j=0;j<b.size();j++)
Add(c[i+j],mul(a[i],b[j]));
return c;
}
while(lim<deg)lim<<=1;init(lim);
a.resize(lim),ntt(a,lim,1);
b.resize(lim),ntt(b,lim,1);
for(int i=0;i<lim;i++)Mul(a[i],b[i]);
ntt(a,lim,-1),a.resize(deg);
return a;
}
多项式求逆:
已知一个次多项式,求多项式满足:
求解过程
倍增:
若已知
平方:
复杂度
注意次数,最高到3倍,开的4倍
代码实现
inline poly Inv(poly a,int deg){
poly c,b(1,ksm(a[0],mod-2));
for(int lim=4;lim<(deg<<2);lim<<=1){
init(lim);
c=a,c.resize(lim>>1);
c.resize(lim),ntt(c,lim,1);
b.resize(lim),ntt(b,lim,1);
for(int i=0;i<lim;i++)Mul(b[i],dec(2,mul(b[i],c[i])));
ntt(b,lim,-1),b.resize(lim>>1);
}b.resize(deg);return b;
}
多项式开方:
已知一个次多项式,求一个意义下的多项式满足
满足
求解过程
倍增
首先当时要满足(否则要二次剩余解,老子不会)
假设已知
由于以上的项是不会有影响的
所以
移项平方得:
复杂度
代码实现
inline poly Sqrt(poly a,int deg){
poly b(1,1),c,d;
for(int lim=4;lim<(deg<<2);lim<<=1){
c=a,c.resize(lim>>1);
init(lim),d=Inv(b,lim>>1),
c.resize(lim),ntt(c,lim,1);
d.resize(lim),ntt(d,lim,1);
for(int i=0;i<lim;i++)Mul(c[i],d[i]);
ntt(c,lim,-1),b.resize(lim>>1);
for(int i=0;i<(lim>>1);i++)b[i]=mul(inv2,add(b[i],c[i]));
}b.resize(deg);return b;
}
多项式除法和取模:
给定一个次多项式和一个次多项式
求一个次多项式和次多项式满足:
求解过程
考虑对一个最高次数为的多项式操作
会发现只是的系数反转的柿子
考虑
最高项为,最高项为,则最高项为,为
两边同时乘一个,并带入
考虑在意义下,已知,最高为不影响,而被消去
则
多项式求逆就可以了
复杂度
代码实现
inline poly operator /(poly a,poly b){
int lim=1,deg=a.size()-b.size()+1;
reverse(a.bg(),a.end());
reverse(b.bg(),b.end());
while(lim<=deg)lim<<=1;
b=Inv(b,lim),b.resize(deg);
a=a*b,a.resize(deg);
reverse(a.bg(),a.end());
return a;
}
inline poly operator %(poly a,poly b){
poly c=a-(a/b)*b;
c.resize(b.size()-1);
return c;
}
多项式求导与积分
我怕是自学的是一个假的微积分
假装自己会的差不多了
复杂度
代码实现
inline poly deriv(poly a){
for(int i=0;i<a.size()-1;i++)a[i]=mul(a[i+1],i+1);
a.pob();return a;
}
inline poly integ(poly a){
a.pb(0);
for(int i=a.size()-1;i;i--)a[i]=mul(a[i-1],inv[i]);
a[0]=0;
return a;
}
多项式Ln
已知一个次多项式,求一个意义下的多项式,满足:
求解过程
由于有公式
若
则
且要满足否则老子不会
求导求逆最后再积分就可以了
复杂度
代码实现
/*
if f(x)=Ln(g(x))
then g'(x)=f'(x)g(x)
*/
inline poly Ln(poly a,int lim){
a=integ(deriv(a)*Inv(a,lim)),a.resize(lim);
return a;
}
多项式Exp
前置知识
泰勒展开
考虑我们要构造一个函数使完全拟合
那首先初始点的值要和一样
在此基础上只需要保证一阶导数,二阶导数……都完全相同即可
即
由于求第阶导数时为
即
所以得证
牛顿迭代
在多项式中一般用来解这类问题:
假设有函数和一个多项式
满足
已知,求
说白了就是用来解之类的方程
首先在的时候即常数时单独求
假设已经知道
要求
考虑将在处泰勒展开
首先显然有
所以最低项次数一定大于
则在意义下,整个式子从开始都为了
则
又
这就大功告成了
例:
比如多项式开根
就是解
假设已知
这时候则
带入
就是我们推出来的式子
已知一个次多项式,求一个意义下的多项式,满足:
也就是
求解过程
倍增:
考虑原问题,即求解方程
同样假设已经知道
令,则
则
复杂度
代码实现
inline poly exp(poly a,int deg){
poly b(1,1),c;a.resize(deg<<1);
for(int lim=2;lim<(deg<<1);lim<<=1){
c=Ln(b,lim);
for(int i=0;i<lim;i++)c[i]=dec(a[i],c[i]);
Add(c[0],1),b=b*c;
b.resize(lim);
}b.resize(deg);return b;
}
多项式多点求值
已知一个次多项式,求
求解过程
考虑构造函数
显然
假设
那显然
但由于是次的,没有起到优化的作用
而考虑对于
则后就只剩下一个常数项,即的值了
但是这样一次就了
考虑分治优化
取模之后的次数减少了一半,继续递归求解即可
可以先分治预处理出来
复杂度
代码实现
第一次写掉了,预处理了一波单位根就跑过去了
也可以在比较小的时候暴力秦九韶展开,然并没写
poly f[N<<2];
int a[N];
int n,m;
#define lc (u<<1)
#define rc ((u<<1)|1)
#define mid ((l+r)>>1)
void build(int u,int l,int r,int *v){
if(l==r){f[u].pb(dec(0,v[l])),f[u].pb(1);return;}
build(lc,l,mid,v),build(rc,mid+1,r,v);
f[u]=f[lc]*f[rc];
}
void calc(int u,int l,int r,poly now,int *v){
if(l==r){v[l]=now[0];return;}
calc(lc,l,mid,now%f[lc],v),calc(rc,mid+1,r,now%f[rc],v);
}
#undef lc
#undef rc
#undef mid
多项式快速插值
考虑传统的拉格朗日插值法插多项式是的
即构造函数
化一下
考虑如何求出
设
就相当于除以了
那就变成了
但是这个分子分母就都是0了,没法直接求
根据洛必达法则:
若
有
同时取导得到
接下来考虑对整个式子分治
设表示分治得到的答案
先分治求出,多点求值把求出来再分治一波就完了
复杂度
下降幂多项式乘法
首先考虑对于构建
考虑对于的点值构造
所以只需要用普通多项式的系数乘个就得到了点值的
点值还原原多项式只需要乘一个即可
其他技巧
多项式快速幂
直接快速幂要多个而且常数大(虽然和常数一样大死个仙人)
当时(要求保证这个),
洛谷板子读入时取模
inline poly ksm(poly a,int deg,int k){
a=exp(Ln(a,deg)*k,deg),a.resize(deg);
return a;
}
分治NTT
Bluestein
混合基NTT
MTT
模板合集
const int mod=998244353,g=3;
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline void Add(int &a,int b){a=add(a,b);}
inline int dec(int a,int b){return a>=b?a-b:a-b+mod;}
inline void Dec(int &a,int b){a=dec(a,b);}
inline int mul(int a,int b){return 1ll*a*b>=mod?1ll*a*b%mod:a*b;}
inline void Mul(int &a,int b){a=mul(a,b);}
inline int ksm(int a,int b,int res=1){for(;b;b>>=1,a=mul(a,a))(b&1)&&(res=mul(res,a));return res;}
const int N=100005,C=17;
int *w[18];
int rev[N<<2];
#define poly vector<int>
#define pb push_back
#define bg begin
inline void init_rev(int lim){
for(int i=0;i<lim;i++)rev[i]=(rev[i>>1]>>1)|((i&1)*(lim>>1));
}
inline void init_w(){
for(int i=1;i<=C;i++)
w[i]=new int[1<<(i-1)];
int wn=ksm(g,(mod-1)/(1<<C));
w[C][0]=1;
for(int i=1;i<(1<<(C-1));i++)w[C][i]=mul(w[C][i-1],wn);
for(int i=C-1;i;i--)
for(int j=0;j<(1<<(i-1));j++)
w[i][j]=w[i+1][j<<1];
}
inline void ntt(poly &f,int lim,int kd){
for(int i=0;i<lim;i++)if(i>rev[i])swap(f[i],f[rev[i]]);
for(int mid=1,l=1;mid<lim;mid<<=1,l++)
for(int i=0;i<lim;i+=(mid<<1))
for(int j=0,a0,a1;j<mid;j++)
a0=f[i+j],a1=mul(f[i+j+mid],w[l][j]),
f[i+j]=add(a0,a1),f[i+j+mid]=dec(a0,a1);
if(kd==-1&&(reverse(f.bg()+1,f.bg()+lim),1))
for(int inv=ksm(lim,mod-2),i=0;i<lim;i++)Mul(f[i],inv);
}
inline poly operator +(poly a,poly b){
poly c;int lim=max(a.size(),b.size());c.resize(lim);
for(int i=0;i<lim;i++)c[i]=add(a[i],b[i]);return c;
}
inline poly operator -(poly a,poly b){
poly c;int lim=max(a.size(),b.size());c.resize(lim);
for(int i=0;i<lim;i++)c[i]=dec(a[i],b[i]);return c;
}
inline poly operator *(poly a,int b){
for(int i=0;i<a.size();i++)Mul(a[i],b);return a;
}
inline poly operator /(poly a,int b){
for(int i=0,inv=ksm(b,mod-2);i<a.size();i++)Mul(a[i],inv);
return a;
}
inline poly operator *(poly a,poly b){
int deg=a.size()+b.size()-1,lim=1;
if(deg<=128){
poly c(deg,0);
for(int i=0;i<a.size();i++)
for(int j=0;j<b.size();j++)
Add(c[i+j],mul(a[i],b[j]));
return c;
}
while(lim<deg)lim<<=1;init(lim);
a.resize(lim),ntt(a,lim,1);
b.resize(lim),ntt(b,lim,1);
for(int i=0;i<lim;i++)Mul(a[i],b[i]);
ntt(a,lim,-1),a.resize(deg);
return a;
}
inline poly Inv(poly a,int deg){
poly c,b(1,ksm(a[0],mod-2));
for(int lim=4;lim<(deg<<2);lim<<=1){
init(lim);
c=a,c.resize(lim>>1);
c.resize(lim),ntt(c,lim,1);
b.resize(lim),ntt(b,lim,1);
for(int i=0;i<lim;i++)Mul(b[i],dec(2,mul(b[i],c[i])));
ntt(b,lim,-1),b.resize(lim>>1);
}b.resize(deg);return b;
}
inline poly Sqrt(poly a,int deg){
poly b(1,1),c,d;
for(int lim=4;lim<(deg<<2);lim<<=1){
c=a,c.resize(lim>>1);
init(lim),d=Inv(b,lim>>1),
c.resize(lim),ntt(c,lim,1);
d.resize(lim),ntt(d,lim,1);
for(int i=0;i<lim;i++)Mul(c[i],d[i]);
ntt(c,lim,-1),b.resize(lim>>1);
for(int i=0;i<(lim>>1);i++)b[i]=mul(inv[2],add(b[i],c[i]));
}b.resize(deg);return b;
}
inline poly operator /(poly a,poly b){
int lim=1,deg=a.size()-b.size()+1;
reverse(a.bg(),a.end());
reverse(b.bg(),b.end());
while(lim<=deg)lim<<=1;
b=Inv(b,lim),b.resize(deg);
a=a*b,a.resize(deg);
reverse(a.bg(),a.end());
return a;
}
inline poly operator %(poly a,poly b){
poly c=a-(a/b)*b;
c.resize(b.size()-1);
return c;
}
inline poly deriv(poly a){
for(int i=0;i<a.size()-1;i++)a[i]=mul(a[i+1],i+1);
a.pob();return a;
}
inline poly integ(poly a){
a.pb(0);
for(int i=a.size()-1;i;i--)a[i]=mul(a[i-1],inv[i]);
a[0]=0;
return a;
}
inline poly Ln(poly a,int lim){
a=integ(deriv(a)*Inv(a,lim)),a.resize(lim);
return a;
}
inline poly exp(poly a,int deg){
poly b(1,1),c;int n=a.size();
for(int lim=2;lim<(deg<<1);lim<<=1){
c=Ln(b,lim);
for(int i=0;i<lim;i++)c[i]=dec(i<n?a[i]:0,c[i]);
Add(c[0],1),b=b*c;
b.resize(lim);
}b.resize(deg);return b;
}
inline poly ksm(poly a,int deg,int k){
a=exp(Ln(a,deg)*k,deg),a.resize(deg);
return a;
}
poly f[N<<2];
#define mid ((l+r)>>1)
#define lc (u<<1)
#define rc ((u<<1)|1)
inline void build(int u,int l,int r,int *a){
if(l==r){
f[u].clear();
f[u].pb(dec(0,a[l]));
f[u].pb(1);return;
}build(lc,l,mid,a),build(rc,mid+1,r,a);
f[u]=f[lc]*f[rc];
}
inline void calc(int u,int l,int r,poly g,int *a){
if(l==r){a[l]=g[0];return;}
calc(lc,l,mid,g%f[lc],a);
calc(rc,mid+1,r,g%f[rc],a);
}
inline void getans(poly a,int *b,int num){
build(1,1,num,b),calc(1,1,num,a,b);
}
#undef mid
#undef lc
#undef rc