衆所周知,多項式全家桶指的是
寫在前面
- 爲了進一步優化,請區分 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祕傳碼風