前言
之前零零碎碎的学了一些多项式的操作(模意义下即NTT)
现在打算还是仔细的学习一下吧,顺带自己推一波式子
然后把板子写一下
多项式乘法
见FFT
朴素的还是挺方便的
当然在一些情况下可以进行优化(暂时咕咕咕)
分治FFT&分治套FFT
这两个东西本质上只是思路,所以没什么好封装的
分治FFT
分治套FFT(这个是猎人杀的题解,里面有讲到分治套FFT)
模意义&循环卷积
一般多项式都有一个操作,模
,即次数大于等于
的项都要舍去
注意这里要和循环卷积分开来,循环卷积是把所有指数大于等于
的项系数都加到次数模
的相应项里
多项式求逆
给定一个多项式
,求模
意义下的逆元
满足
我们发现:
若
,我们可以
求出常数项的逆元
若
,我们考虑递归解决这个问题
设我们要求的为
,即
已经递归求得
,即
容易发现
相减得
我们发现,如果
,显然
,我们的期望结果是
,那么
如果
,那么右边一项在模意义下为
所以,得出如下式子
两边平方
我们发现,所有次数小于
的项至少由一边的一个次数小于
的项乘得,所以可以将模数改为
我们将等式两边同时乘上
得到
移项得
合并得
复杂度
小提示:在具体代码实现的时候可以不用封装的多项式乘法,而只在开始和结束分别做DFT/IDFT即可,中间的运算用每个点值进行运算来解决,能够大大减少常数
多项式除法/取模
给定一个长度为
的多项式
,给定一个长度为
的多项式
求两个多项式
满足
并且
(定义
为多项式
的长度)
容易发现,
注意这里没有模意义下,所以答案是唯一的
传统的除法复杂度是
的,并不优秀
我们发现问题在
上,我们想办法消除影响
有一个很好的思路是:选择一个合适的
使得在这个模意义下的
不受影响,而
被模掉,这样就可以直接套用多项式求逆了
我们发现
那么我们想办法把
的那一部分进行系数翻转
定义对于一个多项式在次数
下的翻转:
(我们发现对于
需要乘上
)
对于式子
我们将
带入(这样等式显然依然成立),得到
等式两边同乘
,得到
我们发现我们发现这个式子只有最后一项的
次数均
,其余项的
次数均
,所以我们把这个式子转到模
意义下即可,得到
我们用多项式求逆
求
,得到
后可以直接用多项式乘法和减法求出
复杂度
多项式微分
由于
的一阶导数为
所以直接做即可
复杂度
多项式积分
根据微分的式子积回来即可,写板子的话常数项默认为
吧
需要预处理逆元
复杂度
多项式牛顿迭代法(思路)
假设我们现在有一个函数
要求,求
满足
多项式牛顿迭代法需要确定初始近似值,即前
位
这
位的值也就是常数项需要单独讨论
假设当前的近似值为
,其确定位数为
我们现在要求
,其确定位数为
已知条件:
我们将
在
上泰勒展开并代入
注意:由于我们这里求的是f(x),所以求导是对f(x)求导而不是对x求导
我们发现,由于
,而我们求的
是在模
意义下的,所以所有
的项都是无用的
所以得到
将
带入并移项得
如果求导后的结果计算很方便,那么就可以迭代求值
多项式开根
承接牛顿迭代的思路
对于一个多项式
,这里
即
我们代入迭代的式子(提示:
的导数为
)
然后常数项需要在模意义下开根(所以如果常数项不是这个模数下的二次剩余那么这个多项式将无法开根,一般题目会说明保证常数项是模意义下的二次剩余,二次剩余博客传送门[待填坑]),当然如果保证常数项为
,那么结果也为
多项式ln
复合函数求导法则:
设
则
我们现在有一个多项式
要求
注意:这里的ln都是对于每个x取确定值的,所以是对x求导
用上面的微分和积分,这些都是
的,求逆
,总复杂度
同开根一样,常数项也需要在模意义下进行
一般写法默认
常数项为
,计算出的
的常数项为
多项式exp
求
化多项式牛顿迭代经典式
设
两边求ln
代入式子
同上,常数项需要单独计算exp
一般写法默认
常数项为
,计算出的
的常数项为
复杂度
多项式k次幂
快速幂:由于每次要IDFT回来保证长度,所以复杂度为
提出最低次数项的次数及
(设为
),再进行ln和exp即可
复杂度
封装代码
贴出封装的代码,所有功能都能很方便的调用
这是loj150挑战多项式的ac代码
二次剩余是拉的
#include<cstdio>
#include<cctype>
#include<cstring>
#include<cstdlib>
#include<vector>
namespace fast_IO
{
const int IN_LEN=10000000,OUT_LEN=10000000;
char ibuf[IN_LEN],obuf[OUT_LEN],*ih=ibuf+IN_LEN,*oh=obuf,*lastin=ibuf+IN_LEN,*lastout=obuf+OUT_LEN-1;
inline char getchar_(){return (ih==lastin)&&(lastin=(ih=ibuf)+fread(ibuf,1,IN_LEN,stdin),ih==lastin)?EOF:*ih++;}
inline void putchar_(const char x){if(oh==lastout)fwrite(obuf,1,oh-obuf,stdout),oh=obuf;*oh++=x;}
inline void flush(){fwrite(obuf,1,oh-obuf,stdout);}
}
using namespace fast_IO;
#define getchar() getchar_()
#define putchar(x) putchar_((x))
typedef long long ll;
#define rg register
template <typename T> inline T max(const T a,const T b){return a>b?a:b;}
template <typename T> inline T min(const T a,const T b){return a<b?a:b;}
template <typename T> inline T mind(T&a,const T b){a=a<b?a:b;}
template <typename T> inline T maxd(T&a,const T b){a=a>b?a:b;}
template <typename T> inline T abs(const T a){return a>0?a:-a;}
template <typename T> inline void swap(T&a,T&b){T c=a;a=b;b=c;}
template <typename T> inline void swap(T*a,T*b){T c=a;a=b;b=c;}
template <typename T> inline T gcd(const T a,const T b){if(!b)return a;return gcd(b,a%b);}
template <typename T> inline T square(const T x){return x*x;};
template <typename T> inline void read(T&x)
{
char cu=getchar();x=0;bool fla=0;
while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}
while(isdigit(cu))x=x*10+cu-'0',cu=getchar();
if(fla)x=-x;
}
template <typename T> void printe(const T x)
{
if(x>=10)printe(x/10);
putchar(x%10+'0');
}
template <typename T> inline void print(const T x)
{
if(x<0)putchar('-'),printe(-x);
else printe(x);
}
const int maxn=2097152,mod=998244353;
inline int Md(const int x){return x>=mod?x-mod:x;}
template<typename T>
inline int pow(int x,T y)
{
rg int res=1;x%=mod;
for(;y;y>>=1,x=(ll)x*x%mod)if(y&1)res=(ll)res*x%mod;
return res;
}
namespace Poly///////namespace of Poly
{
int W_[maxn],FW_[maxn],ha[maxn],hb[maxn],Inv[maxn];
inline void init(const int x)
{
rg int tim=0,lenth=1;
while(lenth<x)lenth<<=1,tim++;
for(rg int i=1;i<lenth;i++)
{
W_[i]=pow(3,(mod-1)/i/2);
FW_[i]=pow(W_[i],mod-2);
}
Inv[0]=Inv[1]=1;
for(rg int i=2;i<x;i++)Inv[i]=(ll)(mod-mod/i)*Inv[mod%i]%mod;
}
int L;
inline void NTT(int*A,const int fla)//prepare:init L
{
for(rg int i=0,j=0;i<L;i++)
{
if(i>j)swap(A[i],A[j]);
for(rg int k=L>>1;(j^=k)<k;k>>=1);
}
for(rg int i=1;i<L;i<<=1)
{
const int w=fla==-1?FW_[i]:W_[i];
for(rg int j=0,J=i<<1;j<L;j+=J)
{
int K=1;
for(rg int k=0;k<i;k++,K=(ll)K*w%mod)
{
const int x=A[j+k],y=(ll)A[j+k+i]*K%mod;
A[j+k]=Md(x+y),A[j+k+i]=Md(mod+x-y);
}
}
}
}
inline std::pair<unsigned int,unsigned int> Mul(std::pair<unsigned int,unsigned int> x,std::pair<unsigned int,unsigned int> y,unsigned int f){
return (std::pair<unsigned int,unsigned int>){((ll)x.first*y.first%mod+(ll)x.second*y.second%mod*f%mod)%mod,((ll)x.second*y.first%mod+(ll)x.first*y.second%mod)%mod};
}
inline int SEC(unsigned int a)
{
if(a<=1) return a;
// if(pow(a,(mod-1)/2)!=1)return -1;
unsigned int x,f;
do x=(((unsigned ll)rand()<<15)^rand())%(a-1)+1;
while(pow(f=((ll)x*x+mod-a)%mod,(mod-1)/2)==1);
std::pair<unsigned int,unsigned int> ans(1,0), t(x,1);
for(int i=(mod+1)/2;i;i>>=1, t=Mul(t, t, f)) if(i&1) ans=Mul(ans, t, f);
// printf("(%u %u)",a,min(ans.first,mod-ans.first));
return min(ans.first,mod-ans.first);
}
struct poly
{
std::vector<int>A;
poly(){A.resize(0);}
poly(const int x){A.resize(1),A[0]=x;}
inline int&operator[](const int x){return A[x];}
inline int operator[](const int x)const{return A[x];}
inline void clear(){A.clear();}
inline unsigned int size()const{return A.size();}
inline void resize(const unsigned int x){A.resize(x);}
void RE(const int x)
{
A.resize(x);
for(rg int i=0;i<x;i++)A[i]=0;
}
void readin(const int MAX)
{
A.resize(MAX);
for(rg int i=0;i<MAX;i++)read(A[i]);
}
void putout()const
{
for(rg unsigned int i=0;i<A.size();i++)print(A[i]),putchar(' ');
}
inline poly operator +(const poly b)const
{
poly RES;
RES.resize(max(size(),b.size()));
for(rg unsigned int i=0;i<RES.size();i++)RES[i]=Md((i<size()?A[i]:0)+(i<b.size()?b[i]:0));
return RES;
}
inline poly operator -(const poly b)const
{
poly RES;
RES.resize(max(size(),b.size()));
for(rg unsigned int i=0;i<RES.size();i++)RES[i]=Md((i<size()?A[i]:0)+mod-(i<b.size()?b[i]:0));
return RES;
}
inline poly operator *(const int b)const
{
poly RES=*this;
for(rg unsigned int i=0;i<RES.size();i++)RES[i]=(ll)RES[i]*b%mod;
return RES;
}
inline poly operator /(const int b)const
{
poly RES=(*this)*pow(b,mod-2);
return RES;
}
inline poly operator *(const poly b)const
{
const int RES=A.size()+b.size()+1;
L=1;while(L<RES)L<<=1;
poly c;c.A.resize(RES);
memset(ha,0,sizeof(int)*L);
memset(hb,0,sizeof(int)*L);
for(rg unsigned int i=0;i<A.size();i++)ha[i]=A[i];
for(rg unsigned int i=0;i<b.A.size();i++)hb[i]=b.A[i];
NTT(ha,1),NTT(hb,1);
for(rg int i=0;i<L;i++)ha[i]=(ll)ha[i]*hb[i]%mod;
NTT(ha,-1);
const int inv=pow(L,mod-2);
for(rg int i=0;i<RES;i++)c.A[i]=(ll)ha[i]*inv%mod;
return c;
}
inline poly inv()const
{
poly C;
if(A.size()==1){C=*this;C[0]=pow(C[0],mod-2);return C;}
C.resize((A.size()+1)>>1);
for(rg unsigned int i=0;i<C.size();i++)C[i]=A[i];
C=C.inv();
L=1;while(L<(int)size()*2-1)L<<=1;
for(rg unsigned int i=0;i<A.size();i++)ha[i]=A[i];
for(rg unsigned int i=0;i<C.size();i++)hb[i]=C[i];
memset(ha+A.size(),0,sizeof(int)*(L-A.size()));
memset(hb+C.size(),0,sizeof(int)*(L-C.size()));
NTT(ha,1),NTT(hb,1);
for(rg int i=0;i<L;i++)ha[i]=(ll)(2-(ll)hb[i]*ha[i]%mod+mod)*hb[i]%mod;
NTT(ha,-1);
const int inv=pow(L,mod-2);
C.resize(size());
for(rg unsigned int i=0;i<size();i++)C[i]=(ll)ha[i]*inv%mod;
return C;
}
/* inline poly inv()const
{
poly C;
if(A.size()==1){C=*this;C[0]=pow(C[0],mod-2);return C;}
C.resize((A.size()+1)>>1);
for(rg unsigned int i=0;i<C.size();i++)C[i]=A[i];
C=C.inv();
poly D=(poly)2-*this*C;
D.resize(size());
D=D*C;
D.resize(size());
return D;
}*///大常数版本
inline void Reverse(const int n)
{
A.resize(n);
for(rg int i=0,j=n-1;i<j;i++,j--)swap(A[i],A[j]);
}
inline poly operator /(const poly B)const
{
poly a=*this,b=B;a.Reverse(size()),b.Reverse(B.size());
b.resize(size()-B.size()+1);
b=b.inv();
b=b*a;
b.Reverse(size()-B.size()+1);
return b;
}
inline poly operator %(const poly B)const
{
poly C=(*this)-(*this)/B*B;C.resize(B.size()-1);
return C;
}
inline poly diff()const
{
poly C;C.resize(size()-1);
for(rg unsigned int i=1;i<size();i++)C[i-1]=(ll)A[i]*i%mod;
return C;
}
inline poly inte()const
{
poly C;C.resize(size()+1);
for(rg unsigned int i=0;i<size();i++)C[i+1]=(ll)A[i]*Inv[i+1]%mod;
C[0]=0;
return C;
}
inline poly ln()const
{
poly C=(diff()*inv()).inte();C.resize(size());
return C;
}
inline poly exp()const
{
poly C;
if(size()==1){C=*this;C[0]=1;return C;}
C.resize((size()+1)>>1);
for(rg unsigned int i=0;i<C.size();i++)C[i]=A[i];
C=C.exp();C.resize(size());
poly D=(poly)1-C.ln()+*this;
D=D*C;
D.resize(size());
return D;
}
inline poly sqrt()const
{
poly C;
if(size()==1)
{
C=*this;C[0]=SEC(C[0]);
return C;
}
C.resize((size()+1)>>1);
for(rg unsigned int i=0;i<C.size();i++)C[i]=A[i];
C=C.sqrt();C.resize(size());
C=(C+*this*C.inv())*(int)499122177;
C.resize(size());
return C;
}
inline poly operator >>(const unsigned int x)const
{
poly C;if(size()<x){C.resize(0);return C;}
C.resize(size()-x);
for(rg unsigned int i=0;i<C.size();i++)C[i]=A[i+x];
return C;
}
inline poly operator <<(const unsigned int x)const
{
poly C;C.RE(size()+x);
for(rg unsigned int i=0;i<size();i++)C[i+x]=A[i];
return C;
}
inline poly Pow(const unsigned int x)const
{
for(rg unsigned int i=0;i<size();i++)
if(A[i])
{
poly C=((((*this/A[i])>>i).ln()*x).exp()*pow(A[i],x))<<(min(i*x,size()));
C.resize(size());
return C;
}
return *this;
}
};
}///////namespace of Poly
Poly::poly a;
int n,m;
int main()
{
Poly::init(maxn);///////namespace of Poly
read(n),n++,read(m);
a.readin(n);
a=(((Poly::poly){2-a[0]}+a-a.sqrt().inv().inte().exp()).ln()+(Poly::poly)1).Pow(m).diff();
a.resize(n-1);
a.putout();
return flush(),0;
}
总结
要注意求导对什么求,否则会推错
很多算法基于牛顿迭代
对于求导积分会损失常数项,所以要单独求
自己写一遍博客才知道很多式子的细节所在