正题
下述的所有例题可在洛谷上搜索“多项式”,找到。
Contents:
- 快速傅里叶变换
- NTT
- 任意模数NTT
- 多项式求逆
- 多项式取模
- 多项式ln
- 多项式exp
- 多项式多点求值
- 多项式快速插值
- 普通多项式转下降幂多项式
- 下降幂多项式转普通多项式
快速傅里叶变换
可以到我的blog学习,尽管写得不太好。
NTT
可以发现
,具体证明可以参考我的blog。所以就可以满足l次单位根的性质。
任意模数NTT
可以发现,如果使用三模数合并或者拆系数NTT只能达到9或7次的DFT。
针对拆系数NTT使用myy优化可以将此升级使得只用4次DFT可以完成。
首先假设现在已经被分成四个多项式。
现在目的是求
我们思路清晰,先求出A和B的DFT,再求出的DFT。
然后设,那么c1(x)的实部就为AC,虚部就为AD,c2同理。
因为A卷E实际上实部虚部可以分开考虑,反正最后对另一部分的影响都是0.
那么我们现在用了5次DFT,其实加上FFT相对于NTT的常数快不了多少。
所以我们还要优化,考虑。
经过一系列推导(大概就是关于AB的方程列出来)有:
这样写只是好看,如果想记得方便可以试试将同一个复数得实部虚部放到一起。
那么我们只需要对Q做一次DFT就可以用O(n)的时间算出A和B的DFT了。
就变成4次DFT了,时间大概是三模数NTT的1/7。
多项式求逆
每一步都很显然,可以直接递归。时间复杂度
多项式取模
多项式取模需要多项式除法,所以咱们一起说。
我们定义一个n次多项式,通俗易懂的来说就是把他的每一项系数翻转过来。
那么
就可以推出上面这些东西,直接用多项式求逆求解G。
然后根据定义求出R就可以了。
在讲多项式ln和多项式exp之前,我们先来说一说泰勒展开这个东西。
泰勒有一天想,对于这些ln和exp这些函数无限项的函数,求出一某个点的值显得很困难。
比如说......
那么泰勒这样想:我们可以用一条有限项的函数来贴近这条函数。
假设这个函数是
那么这个函数需要满足什么样的性质呢?
首先:常数项相等,也就是,其次,他们在
时的导数相等,那么就是
。
他们的二阶导应该也相等:......
那么就有
把,就变成了
。
可以根据导数运算法则自己推一推。
在我的blog里面有常用的泰勒展开公式。
多项式ln
按照我的理解,将在1的位置展开,那么
可以看到,如果第一位为1,那么第n项只会被影响到,所以就会在某个时刻被确定下来,否则永远不会确定下来。
根据复合函数求导法则,可以看到:。
这样就做完了。
多项式exp
按照我的理解,将在0的位置展开,那么
如果多项式的第一位为0,那么第n项只会被影响到,所以就会在某个时刻被确定下来,否则永远不会确定下来。
构造多项式函数
相当于找G的一个零点。
假设前项已经做出来了,
喜闻乐见的,我们将G在的位置展开,得到:
为什么没有后面了?因为平方一定是0.
那么一下解式子可以得到:
做完了,常数极大。
多项式多点求值
可以很容易理解,将a代入一个多项式之后的权值相当于这个多项式对取模的常数项。
可以考虑一个
那么多点求值,我们处理出来,然后分治就可以了,常数极大。
多项式快速插值
首先拉格朗日插值法可以得到:
然后先考虑系数:
那么原来的式子就变成了:
但是你会发现这个G没法求。
就要用到洛必达法则:
那么
求出系数后分治就可以了,先预处理出,常数极大。
普通多项式转下降幂多项式
这个要用到第二类斯特林数。
然后全部带进去就可以得到:
所以我们要求的就是后面那个东西,发现。所以i可以从0开始枚举:
前面可以直接预处理,后面这个需要多点求值,最后卷积即可。
下降幂多项式转普通多项式
比上面那个东西简单,发现我们只需要求出来的点值就可以了。
发现,当时,对于
时,
的贡献是
,那么我们先不管
,最后乘上就可以了。
所以相当于。
后面就是插值的事情了,但是发现慢就慢在求系数的过程。
发现系数也是可以用阶乘预处理出来的,那么系数就很小了。
#include<bits/stdc++.h>
#define vi vector<int>
using namespace std;
const int mod=998244353;
int ksm(int x,int t){
int tot=1;
while(t){
if(t&1) tot=1ll*tot*x%mod;
x=1ll*x*x%mod;
t/=2;
}
return tot;
}
const int N=3000010,I=ksm(3,(mod-1)/4),Iinv=ksm(I,mod-2);
int where[N],limit;
int poor[30000010],inv[N];
int*now=poor;
int*w[2][25];
void pre(){
inv[1]=1;for(int i=2;i<=3000000;i++) inv[i]=1ll*inv[mod%i]*(mod-mod/i)%mod;
for(int l=2,u=0;u<22;u++,l<<=1){
w[0][u]=now;now+=l+1;
w[1][u]=now;now+=l+1;
w[0][u][0]=1;w[0][u][1]=ksm(3,(mod-1)/l);
for(int i=2;i<=l;i++) w[0][u][i]=1ll*w[0][u][i-1]*w[0][u][1]%mod;
for(int i=0;i<=l;i++) w[1][u][i]=w[0][u][l-i];
}
}
void prepare(int n){
int l=0;limit=1;
while(limit<=n) limit<<=1,l++;
for(int i=0;i<limit;i++) where[i]=((where[i>>1]>>1)|((i&1)<<(l-1)));
}
void DFT(vi&now,int op){
for(int i=0;i<limit;i++) if(i<where[i]) swap(now[i],now[where[i]]);
for(int l=1,u=0;l<limit;l<<=1,u++){
for(int i=0;i<limit;i=i+l+l){
for(int x=i,y=i+l,v=0;y<i+l+l;x++,y++,v++){
int a=now[x],b=1ll*now[y]*w[op][u][v]%mod;
now[x]=a+b;now[x]>=mod?now[x]-=mod:0;
now[y]=a+mod-b;now[y]>=mod?now[y]-=mod:0;
}
}
}
if(op){
int tmp=ksm(limit,mod-2);
for(int i=0;i<limit;i++) now[i]=1ll*now[i]*tmp%mod;
}
}
vi operator*(vi f,vi g){
int tot=f.size()+g.size()-2;
prepare(tot);
f.resize(limit);g.resize(limit);
DFT(f,0),DFT(g,0);
for(int i=0;i<limit;i++) f[i]=1ll*f[i]*g[i]%mod;
DFT(f,1);f.resize(tot+1);
return f;
}
vi operator-(vi f,vi g){
int n=max(f.size(),g.size());
if(f.size()==n){
for(int i=0;i<g.size();i++) f[i]=f[i]+mod-g[i],f[i]>=mod?f[i]-=mod:0;
return f;
}
else {
for(int i=0;i<f.size();i++) g[i]=f[i]+mod-g[i],g[i]>=mod?g[i]-=mod:0;
for(int i=f.size();i<n;j++) g[i]=mod-g[i],g[i]>=mod?g[i]-=mod:0;
return g;
}
}
vi operator+(vi f,vi g){
int n=max(f.size(),g.size());
if(f.size()==n){
for(int i=0;i<g.size();i++) f[i]=f[i]+g[i],f[i]>=mod?f[i]-=mod:0;
return f;
}
else {
for(int i=0;i<f.size();i++) g[i]=f[i]+g[i],g[i]>=mod?g[i]-=mod:0;
return g;
}
}
void INV(vi&h){
int n=h.size();
if(n==1) {h[0]=ksm(h[0],mod-2);return ;}
vi f0=h;f0.resize((n+1)/2);INV(f0);
prepare(n+2*(n+1)/2-3);
h.resize(limit);f0.resize(limit);
DFT(h,0);DFT(f0,0);
for(int i=0;i<limit;i++)
h[i]=(2ll*f0[i]+mod-1ll*f0[i]*f0[i]%mod*h[i]%mod)%mod;
DFT(h,1);h.resize(n);
}
vi operator/(vi f,vi g){
int n=f.size(),m=g.size();
reverse(f.begin(),f.end());
reverse(g.begin(),g.end());
f.reszie(n-m+1);g.resize(n-m+1);INV(g);
g=f*g;g.resize(n-m+1);reverse(g.begin(),g.end());
while(g.size() && !g.back()) g.pop_back();
return g;
}
vi operator%(vi f,vi g){
vi ans=f-f/g*g;ans.resize(g.size()-1);
return ans;
}
void LN(vi&h){
int n=h.size();
vi g;g=h;INV(g);
for(int i=0;i<n-1;i++) h[i]=1ll*h[i+1]*(i+1)%mod;
h[n-1]=0;h=h*g;
for(int i=n-1;i>=1;i--) h[i]=1ll*h[i-1]*inv[i]%mod;
h[0]=0;h.resize(n);
}
void EXP(vi&h){
int n=h.size();
if(n==1) {h[0]=1;return ;}
vi f0=h;f0.resize((n+1)/2);EXP(f0);
vi g=f0;g.resize(n);LN(g);
for(int i=0;i<n;i++) h[i]=g[i]+mod-h[i],h[i]>=mod?h[i]-=mod:0;
prepare(n<<1);h.resize(limit);f0.resize(limit);
DFT(h,0),DFT(f0,0);
for(int i=0;i<limit;i++) h[i]=1ll*f0[i]*(mod+1-h[i])%mod;
DFT(h,1);h.resize(n);
}
void KSM(vi&f,int a,int b,bool full){//k mod , k mod-1 , k >= n
int n=f.size(),c,d=n;
for(int i=0;i<n;i++) if(f[i]) {c=f[i],d=i;break;}
if(d && full || 1ll*d*a>=n){
for(int i=0;i<n;i++) f[i]=0;
return ;
}
int tmp=ksm(c,mod-2);c=ksm(c,b);
for(int i=0;i<n-d;i++) f[i]=1ll*f[i+d]*tmp%mod;
f.resize(n-d);LN(f);
for(int i=0;i<n-d;i++) f[i]=1ll*f[i]*a%mod;
EXP(f);f.resize(n);
for(int i=n-1;i>=d*a;i--) f[i]=1ll*f[i-d*a]*c%mod;
for(int i=0;i<d*a;i++) f[i]=0;
}
int Cipolla(int x){
int a=rand()%998244353;
while(ksm(1ll*a*a%mod+mod-x,(mod-1)/2)==1) a=rand()%998244353;
int tmp=1ll*a*a%mod+mod-x;tmp>=mod?tmp-=mod:0;
int x1=1,y1=0,x2=a,y2=1,t=(mod+1)/2;
while(t){
if(t&1){
int tx=x1,ty=y1;
x1=1ll*tx*x2%mod+1ll*ty*y2%mod*tmp%mod,x1>=mod?x1-=mod:0;
y1=1ll*tx*y2%mod+1ll*x2*ty%mod,y1>=mod?y1-=mod:0;
}
int tx=x2,ty=y2;
x2=1ll*tx*tx%mod+1ll*ty*ty%mod*tmp%mod,x1>=mod?x1-=mod:0;
y2=2ll*tx*ty%mod;
t/=2;
}
return min(x1,mod-x1);
}
void KF(vi&f){
int n=f.size();
if(n==1) {f[0]=Cipolla(f[0]);return ;}
vi f0=f;f0.resize((n+1)/2);KF(f0);
vi g=f0;g.resize(n);INV(g);
f=f*g+f0;f.resize(n);
for(int i=0;i<n;i++) f[i]=1ll*f[i]*inv[2]%mod;
}
void SIN(vi&f){
int n=f.size();
vi g=f;
for(int i=0;i<n;i++) f[i]=1ll*f[i]*I%mod,g[i]=1ll*g[i]*(mod-I)%mod;
EXP(f);EXP(g);
f=f-g;
for(int i=0;i<n;i++) f[i]=1ll*f[i]*Iinv%mod*inv[2]%mod;
}
void COS(vi&f){
int n=f.size();
vi g=f;
for(int i=0;i<n;i++) f[i]=1ll*f[i]*I%mod,g[i]=1ll*g[i]*(mod-I)%mod;
EXP(f);EXP(g);f=f+g;
for(int i=0;i<n;i++) f[i]=1ll*f[i]*inv[2]%mod;
}
vi p[N];
void MPpre(vi&a,int now,int l,int r){
if(l==r){
p[now].resize(2);
p[now][1]=1;p[now][0]=mod-a[l];
return ;
}
int mid=(l+r)/2;
MPpre(a,now<<1,l,mid);MPpre(a,now<<1|1,mid+1,r);
p[now]=p[now<<1]*p[now<<1|1];
}
void MPgas(vi f,vi&g,int now,int l,int r){
if(f.size()>=p[now].size()) f=f%p[now];
if(l==r){g[l]=f[0];return ;}
int mid=(l+r)/2;
MPgas(f,g,now<<1,l,mid);MPgas(f,g,now<<1|1,mid+1,r);
}
void MP(vi&f,vi&a,vi&g){
MPpre(a,1,0,a.size()-1);
MPgas(f,g,1,0,a.size()-1);
}
vi FITgas(vi&g,int now,int l,int r){
if(l==r){
vi f;f.resize(1);f[0]=g[l];
return f;
}
int mid=(l+r)/2;
return FITgas(g,now<<1,l,mid)*p[now<<1|1]+FITgas(g,now<<1|1,mid+1,r)*p[now<<1];
}
vi FIT(vector<pair<int,int> >&V){
int n=V.size();
vi a;a.resize(n);
for(int i=0;i<n;i++) a[i]=V[i].first;
MPpre(a,1,0,n-1);
vi g=p[1];
for(int i=0;i<g.size()-1;i++) g[i]=1ll*g[i+1]*(i+1)%mod;
g.resize(g.size()-1);
vi b;b.resize(n);MPgas(g,b,1,0,n-1);
for(int i=0;i<n;i++) b[i]=1ll*V[i].second*ksm(b[i],mod-2)%mod;
return FITgas(b,1,0,n-1);
}

本文深入探讨了多项式在算法中的应用,包括快速傅里叶变换(FFT)、NTT及其变种,以及多项式的求逆、取模、对数、指数等高级运算。文章还介绍了多项式的多点求值、快速插值、转换技巧和优化方法,如myy优化,为读者提供了丰富的算法实践案例。
1462

被折叠的 条评论
为什么被折叠?



