[數學_多項式] NTT與多項式全家桶學習筆記

衆所周知,多項式全家桶指的是

FFT NTT 求逆 帶餘除法 ln exp 快速冪 MTT

寫在前面

  • 爲了進一步優化,請區分 intlong long,儘量 int

NTT

虛單位根常熟大,考慮替代
模意義下原根可以替代

一般取mod=998244353,原根g=3

裏面的一個操作(reverse)需要羣論 證明,gun

Tips

  • 由費馬小定理ap11(modp)a^{p-1}\equiv 1\pmod p得任意aa在模意義下的逆元是ap2a^{p-2}
#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的東西,多項式這裏當然就是多項式啦
A(x)B(x)1(modp(usually xn)) A(x)B(x)\equiv 1(\bmod p(\mathrm{usually}\ x^n))
則稱B(x)B(x)A(x)A(x)的逆元,記爲A1(x)A^{-1}(x)

一頓暴搞得到modxn2\bmod x^{\lceil\frac{n}{2}\rceil}意義下的f01f_0^{-1}變爲modxn\bmod x^{n}意義下的f1f^{-1}的遞歸式是
f1f01(2ff01)(modxn) f^{-1}\equiv f_0^{-1}(2-ff_0^{-1}) \pmod {x^n}

這頓暴搞:
f1f010(modxn2)f22f1f01+f020(modxn)f22f1f01f02(modxn)ff12f01ff02f1f01(2ff01) f^{-1}-f_0^{-1}\equiv0\pmod{x^{\lceil\frac{n}{2}\rceil}}\\ 平方f^{-2}-2f^{-1}f_0^{-1}+f_0^{-2}\equiv 0 \pmod{x^n}\\ 移項f^{-2}\equiv2f^{-1}f_0^{-1}-f_0^{-2}\pmod{x^n}\\ 同乘f\quad f^{-1}\equiv2f_0^{-1}-ff_0^{-2}\\ f^{-1}\equiv f_0^{-1}(2-ff_0^{-1})

操作就遞歸,時間複雜度就O(nlogn)O(n\log n)

Tips

  • 務必保護原多項式
  • xλx^{\lceil \lambda\rceil}以外的全沒有,務必賦零
  • 爲了防止爆 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

求個導再積個分
Under(modxn)B=lnAB=(lnA)×A=AAB=Bdx=AAdx \begin{aligned} &Under\pmod{x^{n}}\\ &B=\ln A\\ \Rightarrow&B'=(\ln A)'\times A'=\frac{A'}{A}\\ \Rightarrow&B=\int B'\mathrm{d}x=\int \frac{A'}{A}\mathrm{d}x \end{aligned}

求導,積分都是O(n)O(n),求逆(求1A\frac{1}{A})乘法都是O(nlogn)O(n\log n)

沒有除法!

直接invip2inv\equiv i^{p-2}複雜度過高
那麼需要逆元,介紹一種方法:
{an}(an=n),pri=j=1iaj,,invn=prnp2(prn)1i=1naiinvi=invi+1×ai+1,,invi=invi×pri1O(N) \begin{aligned} &對於\{a_n\}(通常是a_n=n的數列),記pr_i=\prod_{j=1}^ia_j,\\ &第一遍,inv_n=pr_n^{p-2}(即pr_n的逆元)\equiv\frac{1}{\prod_{i=1}^na_i}\\ &inv_i=inv_{i+1}\times a_{i+1},遞推下去\\ &第二遍,inv_i=inv_i\times pr_{i-1} \\\\&時間複雜度O(N) \end{aligned}

不過我似乎不會這種方法,那麼再來這種好寫的
p,ip=ki+r(k=pi,r=pmodi)ki+r0(modp)i1r1(ki+r)0(modp)i1+kr10(modp)i1kr1(modp)r=pmodi,r<i,ir,11=1 \begin{aligned} &模p意義下,求i的逆元\\ &p=ki+r\quad(k=\lfloor\frac{p}{i}\rfloor,r=p\bmod i)\\ &ki+r\equiv 0\pmod p\\ &i^{-1}r^{-1}(ki+r)\equiv 0\pmod p\\ &i^{-1}+kr^{-1}\equiv 0\pmod p\\ &i^{-1}\equiv -kr^{-1}\pmod p\\ &由於r=p\bmod i,則r<i,求i時r已求出,可以遞歸求解\\ &邊界1^{-1}=1 \end{aligned}

#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

已知g(x)g(x),求f(x)f(x),其中
g(f(x))0(modxn) g(f(x))\equiv0\pmod {x^n}
前置知識Talor’s Expansion(寫這篇博客的時候本蒟蒻還很菜,當然現在也還很菜,大家將就着看)
牛頓迭代,考慮倍增

已知g(f0)0(modxn2)g(f_0)\equiv 0\pmod {x^{\lceil\frac{n}{2}\rceil}}
要求g(f)0(modxn)g(f)\equiv 0\pmod{x^n}
g(f)g(f)f0f_0處泰勒展開
i=1+g(i)(f0)i!(ff0)i0(modxn) \sum_{i=1}^{+\infin}\frac{g^{(i)}(f_0)}{i!}(f-f_0)^i\equiv0\pmod{x^n}\\
容易知道ff0f-f_0xn2xnx^{\lceil\frac{n}{2}\rceil}\to x^n次的,那麼當i2i\geq 2時,(ff0)i0(modxn)(f-f_0)^i\equiv0\pmod{x^n}
所以
g(f0)+g(f0)(ff0)0(modxn)fg(f0)g(f0)f0g(f0)(modxn)ff0g(f0)g(f0)(modxn) \begin{aligned} g(f_0)+g'(f_0)\cdot (f-f_0)&\equiv0\pmod{x^n}\\ f\cdot g'(f_0)&\equiv g'(f_0)\cdot f_0-g(f_0)\pmod{x^n}\\ f&\equiv f_0-\frac{g(f_0)}{g'(f_0)}\pmod{x^n} \end{aligned}
最下面的那個柿子是結論,建議背住

此外,邊界情況依題而定

exp

已知AA,求B=eAB=e^A(保證A(0)=0A(0)=0,即AA的常數項爲00,因爲如果不是這樣你求出來的東西就不是整數了)
B=eAlnB=AlnBA=0,,BAg(B)=lnBA=0()B=B0g(B0)g(B0)g(B)=dgdB(,)=1B=B1B=B0lnB0AB01=B0(lnB0A)B0=B0(1+lnB0A) \begin{aligned} &B=e^A\Rightarrow\ln B=A\\ &\ln B-A=0,興奮地把它看做一個函數,B爲變量,A爲常數\\ &記爲g(B)=\ln B-A=0(即求零點,於是考慮牛頓迭代)\\ &B=B_0-\frac{g(B_0)}{g'(B_0)}\\ &g'(B)=\frac{\mathrm{d}g}{\mathrm{d}B}(特別寫出來,注意變量)=\frac{1}{B}=B^{-1}\\ B&=B_0-\frac{\ln B_0-A}{B_0^{-1}}\\ &=B_0-(\ln B_0-A)B_0\\ &=B_0(1+\ln B_0-A) \end{aligned}
倍增做

邊界:n=0,B=e0=1n=0時, B=e^0=1
注意數組清零

#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;
}

多項式快速冪

弱化版

顯然不能按普通數快速冪的方法算,這kk太大了
考慮推導
B=AkB=eklnA B=A^k\\ B=e^{k\ln A}
那就先ln,然後exp
顯然ln的定義域不能有0,也就是弱化版和加強版的區別

由於k太大,利用某個模的性質xkxkmodp(modp)x^k\equiv x^{k\bmod p}\pmod p

#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;
}

加強版

  1. 想辦法避開00,有00的位置跳過,一直跳到沒零爲止
    00前最低位若爲xnx^n,那麼快速冪之後最低位應爲xnkx^{nk},跳回去即可
  2. a01a_0\neq1時,你也不知道遞歸到11時的次冪,那就除掉它

注意模了的kk不是原來的kk
綜上所述,我們需要記錄的內容有沒有模過的kk和乘除a0a_0的冪次(注意費馬小定理)

多項式開平方根

弱化版

推柿子(就懶得寫同餘了,反正都能理解)
B=AB2A=0,B2A=g(B)B=B0g(B0)gB0=B0B02A2B0=12(B0+AB0) \begin{aligned} &B=\sqrt A\\ &B^2-A=0,記B^2-A=g(B)\\ &顯然牛頓迭代\\ B&=B_0-\frac{g(B_0)}{g'B_0}\\ &=B_0-\frac{B_0^2-A}{2B_0}\\ &=\frac{1}{2}(B_0+\frac{A}{B_0}) \end{aligned}
後面兩個先咕了,去學習一下sszx碼風/sszx祕傳碼風

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章