众所周知,多项式全家桶指的是
写在前面
- 为了进一步优化,请区分 int 和 long long,尽量 int
NTT
虚单位根常熟大,考虑替代
模意义下原根可以替代
一般取mod=998244353,原根g=3
里面的一个操作(reverse)需要群论 证明,gun
Tips
- 由费马小定理得任意在模意义下的逆元是
#include<bits/stdc++.h>
using namespace std;
#define in Read()
#define int long long
inline int in{
int i=0,f=1;char ch=0;
while(ch!='-'&&!isdigit(ch)) ch=getchar();
if(ch=='-') ch=getchar(),f=-1;
while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int NNN=5e6+5;
const int MOD=998244353;
const int G=3;
int n,m,rev[NNN];
int A[NNN],B[NNN],C[NNN];
inline int qpow(int a,int x){
int res=1;
while(x){
if(x&1) res=res*a%MOD;
a=a*a%MOD;
x>>=1;
}
return res;
}
inline void NTT(int *f,int lim,int sgn){
for(int i=0;i<lim;++i)
if(i<rev[i]) swap(f[i],f[rev[i]]);
for(int len=1;len<lim;len<<=1){
int siz=len<<1;
int wn=qpow(G,(MOD-1)/siz);
for(int l=0;l<lim;l+=siz){
int w=1;
for(int i=l;i<l+len;++i){
int a=f[i],b=f[i+len]*w%MOD;
f[i]=(a+b)%MOD;
f[i+len]=(a-b+MOD)%MOD;
w=w*wn%MOD;
}
}
}
if(sgn) return;
int inv=qpow(lim,MOD-2);//inverse element of 'limit'
reverse(f+1,f+lim);//Just recite it babe
for(int i=0;i<lim;++i)
f[i]=f[i]*inv%MOD;
}
signed main(){
n=in,m=in;
for(int i=0;i<=n;++i) A[i]=in;
for(int i=0;i<=m;++i) B[i]=in;
int lim=1;
while(lim<=n+m) lim<<=1;
for(int i=0;i<lim;++i) rev[i]=(rev[i>>1]>>1)|((i&1)?lim>>1:0);
NTT(A,lim,1);
NTT(B,lim,1);
for(int i=0;i<lim;++i) C[i]=A[i]*B[i]%MOD;
// for(int i=1;i<=lim;++i)
// printf("%lld %lld\n",A[i],B[i]);
NTT(C,lim,0);
for(int i=0;i<=n+m;++i) printf("%lld ",C[i]);
return 0;
}
求逆
逆元的定义是模意义下,与本原积为1的东西,多项式这里当然就是多项式啦
则称为的逆元,记为
一顿暴搞得到意义下的变为意义下的的递归式是
这顿暴搞:
操作就递归,时间复杂度就
Tips
- 务必保护原多项式
- 在以外的全没有,务必赋零
- 为了防止爆 int 之类的东西,请在乘的时候多乘个 1ll
- 求逆的时候有个数组拿来临时存装原多项式的信息,如果在函数里面定义它就炸了,定义在外面就没问题(由于递归的性质,定义在外面是不影响正确性的)
#include<bits/stdc++.h>
using namespace std;
#define in Read()
typedef long long ll;
inline int in{
int i=0,f=1;char ch=0;
while(ch!='-'&&!isdigit(ch)) ch=getchar();
if(ch=='-') ch=getchar(),f=-1;
while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int NNN=5e5+5;
const ll MOD=998244353;
int n,rev[NNN];
ll A[NNN],I[NNN],h[NNN];
inline ll qpow(ll a,int x){
ll res=1;
while(x){
if(x&1) res=res*a%MOD;
a=a*a%MOD;
x>>=1;
}
return res;
}
inline void NTT(ll *f,int lim,int sgn){
for(int i=0;i<lim;++i)
if(i<rev[i]) swap(f[i],f[rev[i]]);
for(int len=1;len<lim;len<<=1){
int siz=len<<1;
ll wn=qpow(3,(MOD-1)/siz);
for(int l=0;l<lim;l+=siz){
ll w=1;
for(int i=l;i<l+len;++i){
ll a=f[i],b=f[i+len]*w%MOD;
f[i]=(a+b)%MOD;
f[i+len]=(a-b+MOD)%MOD;
w=w*wn%MOD;
}
}
}
if(sgn) return;
int inv=qpow(lim,MOD-2);
reverse(f+1,f+lim);
for(int i=0;i<lim;++i)
f[i]=f[i]*inv%MOD;
}
inline void Inv(int len,ll *f,ll *g){
if(len==1){
g[0]=qpow(f[0],MOD-2);
return;
}
Inv((len+1)>>1,f,g);//ceil len
int lim=1;
while(lim<len) lim<<=1;lim<<=1;
for(int i=0;i<lim;++i) rev[i]=(rev[i>>1]>>1)|((i&1)?lim>>1:0);
for(int i=0;i<len;++i) h[i]=f[i];
for(int i=len;i<lim;++i) h[i]=0;
NTT(h,lim,1);
NTT(g,lim,1);
for(int i=0;i<lim;++i) g[i]=1ll*(1ll*2-1ll*g[i]*h[i]%MOD+MOD)%MOD*g[i]%MOD;
NTT(g,lim,0);
for(int i=len;i<lim;++i) g[i]=0;
return;
}
int main(){
n=in;
for(int i=0;i<n;++i) A[i]=in;
Inv(n,A,I);
for(int i=0;i<n;++i) printf("%d ",I[i]);
return 0;
}
ln
求个导再积个分
求导,积分都是,求逆(求)乘法都是
没有除法!
直接复杂度过高
那么需要逆元,介绍一种方法:
不过我似乎不会这种方法,那么再来这种好写的
#include<bits/stdc++.h>
using namespace std;
#define in Read()
typedef long long ll;
inline int in{
int i=0,f=1;char ch=0;
while(ch!='-'&&!isdigit(ch)) ch=getchar();
if(ch=='-') ch=getchar(),f=-1;
while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int NNN=5e5+5;
const ll MOD=998244353;
int n,rev[NNN];
ll int_inv[NNN],prod[NNN];
ll A[NNN],B[NNN],C[NNN],D[NNN],E[NNN];
//B=lnA target polynomial
//C=A' differential coefficient
//D is inversion of A
//B is integral of A
//C<-A, D<-A, E=C*D, B<-E
inline ll qpow(ll a,int x){
ll res=1;
while(x){
if(x&1) res=res*a%MOD;
a=a*a%MOD;
x>>=1;
}
return res;
}
ll h[NNN];
inline void NTT(ll *f,int lim,int sgn){
for(int i=0;i<lim;++i)
if(i<rev[i]) swap(f[i],f[rev[i]]);
for(int len=1;len<lim;len<<=1){
int siz=len<<1;
ll wn=qpow(3,(MOD-1)/siz);
for(int l=0;l<lim;l+=siz){
ll w=1;
for(int i=l;i<l+len;++i){
ll a=f[i],b=f[i+len]*w%MOD;
f[i]=(a+b)%MOD;
f[i+len]=(a-b+MOD)%MOD;
w=w*wn%MOD;
}
}
}
if(sgn) return;
ll inv=qpow(lim,MOD-2);
reverse(f+1,f+lim);
for(int i=0;i<lim;++i)
f[i]=f[i]*inv%MOD;
return;
}
inline void Inv(int len,ll *f,ll *g){
if(len==1){
g[0]=qpow(f[0],MOD-2);
return;
}
Inv((len+1)>>1,f,g);
int lim=1;
while(lim<len) lim<<=1;lim<<=1;
for(int i=0;i<lim;++i) rev[i]=(rev[i>>1]>>1)|((i&1)?lim>>1:0);
for(int i=0;i<len;++i) h[i]=f[i];
for(int i=len;i<lim;++i) h[i]=0;
NTT(h,lim,1);
NTT(g,lim,1);
for(int i=0;i<lim;++i) g[i]=1ll*(2ll-1ll*g[i]*h[i]%MOD+MOD)%MOD*g[i]%MOD;
NTT(g,lim,0);
for(int i=len;i<lim;++i) g[i]=0;
return;
}
int main(){
n=in;
for(int i=0;i<n;++i) A[i]=in;
for(int i=0;i<n;++i) C[i]=A[i+1]*(i+1)%MOD;
Inv(n,A,D);
int lim=1;
while(lim<(n<<1)-1) lim<<=1;
for(int i=0;i<lim;++i) rev[i]=(rev[i>>1]>>1)|((i&1)?lim>>1:0);
NTT(C,lim,1);
NTT(D,lim,1);
for(int i=0;i<lim;++i) E[i]=C[i]*D[i];
NTT(E,lim,0);
int_inv[1]=1;
for(int i=2;i<lim;++i) int_inv[i]=(MOD-MOD/i)*int_inv[MOD%i]%MOD;
for(int i=0;i<lim;++i) B[i+1]=(E[i]%MOD+MOD)%MOD*int_inv[i+1]%MOD;
for(int i=0;i<n;++i) printf("%d ",B[i]);
}
牛顿迭代
心态爆炸,想学一个,发现前置知识是另一个,然后就递归了qwq
话说这不是拓扑排序吗qwqwq
已知,求,其中
前置知识Talor’s Expansion(写这篇博客的时候本蒟蒻还很菜,当然现在也还很菜,大家将就着看)
牛顿迭代,考虑倍增
已知
要求
对在处泰勒展开
容易知道是次的,那么当时,
所以
最下面的那个柿子是结论,建议背住
此外,边界情况依题而定
exp
已知,求(保证,即的常数项为,因为如果不是这样你求出来的东西就不是整数了)
倍增做
边界:
注意数组清零
#include<bits/stdc++.h>
using namespace std;
#define in Read()
typedef long long ll;
inline int in{
int i=0,f=1;char ch=0;
while(ch!='-'&&!isdigit(ch)) ch=getchar();
if(ch=='-') ch=getchar(),f=-1;
while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int NNN=5e5+5;
const ll mod=998244353;
int rev[NNN],lim;
inline ll qpow(ll a,int x){
ll res=1;
while(x){
if(x&1) res=res*a%mod;
a=a*a%mod;
x>>=1;
}
return res;
}
inline void Init(int len){
lim=1;
while(lim<len) lim<<=1;
for(int i=0;i<lim;++i)
rev[i]=(rev[i>>1]>>1)|((i&1)?lim>>1:0);
return;
}
inline void NTT(ll *f,int sgn){
for(int i=0;i<lim;++i)
if(i<rev[i]) swap(f[i],f[rev[i]]);
for(int len=1;len<lim;len<<=1){
int siz=len<<1;
ll wn=qpow(3,(mod-1)/siz);
for(int l=0;l<lim;l+=siz){
ll w=1;
for(int i=l;i<l+len;++i){
ll a=f[i],b=f[i+len]*w%mod;
f[i]=(a+b)%mod;
f[i+len]=(a-b+mod)%mod;
w=w*wn%mod;
}
}
}
if(sgn) return;
int inv=qpow(lim,mod-2);
reverse(f+1,f+lim);
for(int i=0;i<lim;++i)
f[i]=f[i]*inv%mod;
return;
}
//After successfully debuged, come back and try to make it `inline ll *Con(...)`
inline void Con(ll *f,ll *g,int lf,int lg){//Convolve h=f*g a.k.a.卷积
Init(lf+lg);
NTT(f,1);
NTT(g,1);
for(int i=0;i<lim;++i) f[i]=f[i]*g[i]%mod;
NTT(f,0);
return;
}
ll tmp_inv[NNN];
inline void Inv(ll *f,ll *g,int len){//inversion: fg=1, f=>g
#define h tmp_inv
if(len==1){
g[0]=qpow(f[0],mod-2);
return;
}
Inv(f,g,(len+1)>>1);
Init(len<<1);
for(int i=0;i<len;++i) h[i]=f[i];
for(int i=len;i<lim;++i) h[i]=0;
NTT(g,1);
NTT(h,1);
for(int i=0;i<lim;++i) g[i]=1ll*(2ll-1ll*h[i]*g[i]%mod+mod)%mod*g[i]%mod;
NTT(g,0);
for(int i=len;i<lim;++i) g[i]=0;
return;
#undef h
}
inline void Der(ll *f,ll *g,int len){//derivation: g=f' a.k.a.求导
for(int i=0;i<len-1;++i) g[i]=f[i+1]*(i+1)%mod;
g[len-1]=0;
return;
}
ll tmp1_int[NNN],tmp2_int[NNN];
inline void Int(ll *f,ll *g,int len){//integral: g=\int f a.k.a.积分
#define inv tmp1_int
#define pr tmp2_int
pr[1]=1;
for(int i=2;i<=len;++i) pr[i]=pr[i-1]*i%mod;
inv[len]=qpow(pr[len],mod-2);
for(int i=len-1;i;--i) inv[i]=inv[i+1]*(i+1)%mod;
inv[1]=1;
for(int i=2;i<=len;++i) inv[i]=inv[i]*pr[i-1]%mod;
for(int i=1;i<len;++i) g[i]=f[i-1]*inv[i]%mod;
g[0]=0;
for(int i=0;i<=len;++i) inv[i]=pr[i]=0;
return;
#undef inv
#undef pr
}
ll tmp1_ln[NNN],tmp2_ln[NNN];
inline void Ln(ll *f,ll *g,int len){//napierian: g=ln f a.k.a.ln
#define p1 tmp1_ln//p1 for derivation
#define p2 tmp2_ln//p2 for inversion
Der(f,p1,len);
Inv(f,p2,len);
Con(p1,p2,len-1,len);
Int(p1,g,len);
for(int i=len;i<lim;++i) g[i]=0;
for(int i=0;i<lim;++i) p1[i]=p2[i]=0;
return;
#undef p1
#undef p2
}
ll tmp1_exp[NNN];
inline void Exp(ll *f,ll *g,int len){//exponential: g=e^f a.k.a.exp
#define h tmp1_exp
if(len==1){
g[0]=1;
return;
}
Exp(f,g,(len+1)>>1);
Ln(g,h,len);
for(int i=0;i<len;++i) h[i]=(f[i]-h[i]+mod)%mod;
for(int i=len;i<lim;++i) h[i]=0;
h[0]=(h[0]+1)%mod;
Con(g,h,len,len);
for(int i=0;i<lim;++i) h[i]=0;
for(int i=len;i<lim;++i) g[i]=0;
return;
#undef h
}
ll A[NNN],B[NNN];
int n;
int main(){
n=in;
for(int i=0;i<n;++i) A[i]=in;
Exp(A,B,n);
for(int i=0;i<n;++i) printf("%lld ",B[i]);
return 0;
}
多项式快速幂
弱化版
显然不能按普通数快速幂的方法算,这太大了
考虑推导
那就先ln,然后exp
显然ln的定义域不能有0,也就是弱化版和加强版的区别
由于k太大,利用某个模的性质
#include<bits/stdc++.h>
using namespace std;
#define in Read()
typedef long long ll;
inline ll in{
ll i=0,f=1;char ch=0;
while(ch!='-'&&!isdigit(ch)) ch=getchar();
if(ch=='-') ch=getchar(),f=-1;
while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int NNN=5e5+5;
const ll mod=998244353;
int lim,rev[NNN];
inline ll get_k(){
ll i=0;char ch=0;
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) i=(i*10%mod+ch-48)%mod,ch=getchar();
return i;
}
inline ll qpow(ll a,int x){
ll res=1;
while(x){
if(x&1) res=res*a%mod;
a=a*a%mod;
x>>=1;
}
return res;
}
inline void Init(int len){
lim=1;
while(lim<len) lim<<=1;
for(int i=0;i<lim;++i)
rev[i]=(rev[i>>1]>>1)|((i&1)?lim>>1:0);
return;
}
inline void NTT(ll *f,int sgn){
for(int i=0;i<lim;++i)
if(i<rev[i]) swap(f[i],f[rev[i]]);
for(int len=1;len<lim;len<<=1){
int siz=len<<1;
ll wn=qpow(3,(mod-1)/siz);
for(int l=0;l<lim;l+=siz){
ll w=1;
for(int i=l;i<l+len;++i){
ll a=f[i],b=f[i+len]*w%mod;
f[i]=(a+b)%mod;
f[i+len]=(a-b+mod)%mod;
w=w*wn%mod;
}
}
}
if(sgn) return;
ll inv=qpow(lim,mod-2);
reverse(f+1,f+lim);
for(int i=0;i<lim;++i)
f[i]=f[i]*inv%mod;
return;
}
inline void Con(ll *f,ll *g,int lf,int lg){
Init(lf+lg);
NTT(f,1);
NTT(g,1);
for(int i=0;i<lim;++i) f[i]=f[i]*g[i]%mod;
NTT(f,0);
return;
}
inline void Der(ll *f,ll *g,int len){
for(int i=0;i<len-1;++i) g[i]=f[i+1]*(i+1)%mod;
g[len-1]=0;
return;
}
ll tmp_inv[NNN];
inline void Inv(ll *f,ll *g,int len){
#define h tmp_inv
if(len==1){
g[0]=qpow(f[0],mod-2);
return;
}
Inv(f,g,(len+1)>>1);
Init(len<<1);
for(int i=0;i<len;++i) h[i]=f[i];
for(int i=len;i<lim;++i) h[i]=0;
NTT(g,1);
NTT(h,1);
for(int i=0;i<lim;++i) g[i]=1ll*(2ll-1ll*g[i]*h[i]%mod+mod)%mod*g[i]%mod;
NTT(g,0);
for(int i=len;i<lim;++i) g[i]=0;
return;
#undef h
}
ll tmp_int[NNN],pro_int[NNN];
inline void Int(ll *f,ll *g,int len){
#define inv tmp_int
#define pro pro_int
pro[1]=1;
for(int i=2;i<=len;++i) pro[i]=pro[i-1]*i%mod;
inv[len]=qpow(pro[len],mod-2);
for(int i=len-1;i;--i) inv[i]=inv[i+1]*(i+1)%mod;
inv[1]=1;
for(int i=2;i<=len;++i) inv[i]=inv[i]*pro[i-1]%mod;
for(int i=1;i<len;++i) g[i]=f[i-1]*inv[i]%mod;
g[0]=0;
for(int i=0;i<=len;++i) inv[i]=pro[i]=0;
return;
#undef inv
#undef pro
}
ll tmp1_ln[NNN],tmp2_ln[NNN];
inline void Ln(ll *f,ll *g,int len){
#define p1 tmp1_ln
#define p2 tmp2_ln
Der(f,p1,len);
Inv(f,p2,len);
Con(p1,p2,len-1,len);
Int(p1,g,len);
for(int i=len;i<lim;++i) g[i]=0;
for(int i=0;i<lim;++i) p1[i]=p2[i]=0;
return;
#undef p1
#undef p2
}
ll tmp_exp[NNN];
inline void Exp(ll *f,ll *g,int len){
#define h tmp_exp
if(len==1){
g[0]=1;
return;
}
Exp(f,g,(len+1)>>1);
Ln(g,h,len);
for(int i=0;i<len;++i) h[i]=(f[i]-h[i]+mod)%mod;
for(int i=len;i<lim;++i) h[i]=0;
h[0]=(h[0]+1)%mod;
Con(g,h,len,len);
for(int i=0;i<lim;++i) h[i]=0;
for(int i=len;i<lim;++i) g[i]=0;
return;
#undef h
}
ll tmp_pow[NNN];
inline void polypow(ll *f,ll *g,int len,int k){
#define tmp tmp_pow
Ln(f,tmp,len);
for(int i=0;i<len;++i) tmp[i]=tmp[i]*k%mod;
Exp(tmp,g,len);
return;
#undef tmp
}
int n;
ll k,A[NNN],B[NNN];
int main(){
n=in,k=get_k();
for(int i=0;i<n;++i) A[i]=in;
polypow(A,B,n,k);
for(int i=0;i<n;++i) printf("%lld ",B[i]);
return 0;
}
加强版
- 想办法避开,有的位置跳过,一直跳到没零为止
跳前最低位若为,那么快速幂之后最低位应为,跳回去即可 - 时,你也不知道递归到时的次幂,那就除掉它
注意模了的不是原来的
综上所述,我们需要记录的内容有没有模过的和乘除的幂次(注意费马小定理)
多项式开平方根
弱化版
推柿子(就懒得写同余了,反正都能理解)
后面两个先咕了,去学习一下sszx码风/sszx秘传码风