多項式:從門都沒入到剛邁過門檻


多項式:從門都沒入到剛邁過門檻

還記得我上次講多項式的文章么?經過CSP的洗禮和最近一段時間的學習后,我對多項式的理解又加深了吧。

初三黨文化課壓力是大,盡管上次月考年級第一,但是文化課還是不能掉以輕心,畢竟要中考,也不能沒學上吧。

零、快速數論變換(NTT)

多項式乘法是多項式其他操作的一個基礎。上次我們研究了一下FFT。但是不少題目都是要求取模的。顯然,FFT中的復數是不能取模的。而且,很多時候題目中多項式的系數也都是整數。這個時候,NTT就派上用場了!

前置知識:原根

(其實我本來是打算寫一篇博客講這玩意的,但是被我咕了)

階:當 \((a,m)=1\)時,令最小的\(x\)使得\(a^x\equiv 1\pmod{m}\)\(a\pmod{m}\)的階。

有一個很重要的性質:\(x|\varphi(m)\)

\(a\)的取值為\(g\)時,如果它的階就是\(\varphi(m)\),那么我們稱\(g\)\(m\)的原根。

原根怎么求呢?考慮到大部分情況下原根都比較小,我們只需從2開始枚舉原根,然后從\(1\)\(\varphi(m)\)都試一下就行了。

常見的模數\(998244353,1004535809,469762049\)的原根都是\(3\)

下面是關於原根的幾個結論。我這里就不證明了。(畢竟重點在NNT不在原根上)

1.\(g^0,g^1,g^2,\dots,g^{\varphi(m)}\)兩兩不同,構成了\(m\)的完全剩余系。

2.只有\(2,4,p^k,2p^k\)存在原根。

3.若\(g\)\(p\)的原根,\(g\)也是\(p^k\)原根。

我們回顧一下FFT的過程。FFT結合了單位根的美妙性質,完成了蝴蝶操作,從而使復雜度降低為\(O(n \log n)\)

而在模意義下,原根也具有單位根的性質!

我們只需要把FFT中的\(\pi\)換成模數,把單位根換成原根,就是NTT了!

核心代碼:

inline void NTT(int *a,int len,int f){
	for(int i=0;i<len;i++) if(i<r[i]) swap(a[i],a[r[i]]);
	for(int mid=1;mid<len;mid<<=1){//枚舉合並區間的一半長度
		int wn=ksm((f==1?g:gi),(MOD-1)/(mid<<1));//求當前的原根
		for(int j=0;j<len;j+=(mid<<1)){//進行蝴蝶操作
			int w=1;
			for(int k=j;k<j+mid;k++){
				int u=a[k];
				int t=(1ll*w*a[k+mid])%MOD;
				a[k]=(u+t)%MOD;
				a[k+mid]=(u-t+MOD)%MOD;
				w=(1ll*w*wn)%MOD;
			}
		}
	} 
}

那么NTT和FFT的效率對比?

1

1

好吧,至少在這道題里,以及我寫的代碼里,NTT是完爆FFT的。

在我們開始多項式的其他操作之前,我們先給大家講個概念:卷積

設有兩個數列\(a\)\(b\),那么這兩個數列的卷積\(c\)定義為:

\[c_k=\sum_{i=0}^ka_ib_{k-i} \]

我們常常記作:

\[c=a*b \]

我們很容易的得到,多項式的乘法與多項式的卷積是一致的:

\(F(x)=\sum a_i x^i\)\(G(x)=\sum b_ix^i\)\(A(x)=\sum c_ix^i\),且\(c=a*b\),於是我們可以得到:

\[F=A*G \]

下面就讓我們看看多項式的一些其他操作吧:

一、多項式求逆

已知一個多項式\(G(x)\),求一個多項式\(F(x)\),使得:

\[F(x)G(x)\equiv 1 \pmod{x^n} \]

\(n=1\)時,\(G(x)\)是一個常數\(c\),那么,\(F(x)=c^{-1}\)

假設我們已經知道了一個多項式\(F_0(x)\),使得:

\[F_0(x)G(x)\equiv 1 \pmod{x^{\lceil n/2 \rceil}} \]

那么顯然,我們有:

\[F(x)G(x)\equiv 1 \pmod{x^{\lceil n/2 \rceil}} \]

將上述兩式相減,我們得到:

\[G(x)(F(x)-F_0(x))\equiv 0 \pmod{ x^{\lceil x/2 \rceil}} \]

由於\(G(x)\)有常數項,所以我們有:

\[F(x)-F_0(x)\equiv 0 \pmod{x^{\lceil n/2 \rceil}} \]

將上式平方得:

\[(F(x)-F_0(x))^2\equiv 0 \pmod{x^n} \]

展開:

\[F(x)^2 -2F(x)F_0(x)+F_0(x)^2 \equiv 0 \pmod{x^n} \]

因為\(F(x)\)現在是二次的,我們想方法把他變成一次。根據最開始的式子,我們有:

\[F(x)-2F_0(x)+G(x)F_0(x)^2\equiv0 \pmod{x^n} \]

所以:

\[F(x)\equiv 2F_0(x)-G(x)F_0(x)^2 \pmod{x^n} \]

每次多項式乘法是\(O(n \log n )\)的,所以:

\[T(n)=T(n/2)+O(n \log n) = O(n \log n) \]

核心代碼:

inline void P_inv(int* a,int* ans,int n){
	static int tmp[maxn<<1];
	if(n==1){
		ans[0]=ksm(a[0],MOD-2);
		return;
	}
	P_inv(a,ans,(n+1)>>1);
	int len=1,cnt=0;
	while(len<(n<<1)) len<<=1,cnt++;
	for(int i=0;i<len;i++) r[i]=(r[i>>1]>>1)|((i&1)<<(cnt-1));
	for(int i=0;i<n;i++) tmp[i]=a[i];
	for(int i=n;i<len;i++) tmp[i]=0;
	NTT(tmp,len,1); NTT(ans,len,1);
	for(int i=0;i<len;i++) ans[i]=1ll*(2ll-1ll*tmp[i]*ans[i]%MOD+MOD)%MOD*ans[i]%MOD;
	NTT(ans,len,-1);
	for(int i=n;i<len;i++) ans[i]=0;
}

二、多項式\(\ln\)

已知一個多項式\(G(x)\),求一個多項式\(F(x)\),使得:

\[F(x)\equiv \ln G(x) \pmod{x^n} \]

等一下,多項式有ln這種東西?

沒錯,它有,定義如下:

\[\ln(1-F(x))=-\sum_{i\ge 0}\frac{F^i(x)}{i} \]

事實上,你可以把它看作多項式與麥克勞林級數的復合。

所以根據定義,它的常數項一定為1,否則不存在ln了。

見到\(\ln\),我們一般都是要求導的:

\[F'(x)\equiv \frac{G'(x)}{G(x)} \pmod{x^n} \]

然后我們再積分就行了:

\[F(x)\equiv \int \frac{G'(x)}{G(x)}\,dx \pmod{x^n} \]

時間復雜度\(O(n \log n)\)

核心代碼:

inline void P_der(int *a,int *ans,int n){
	for(int i=1;i<n;i++) ans[i-1]=1ll*i*a[i]%MOD;
	ans[n-1]=0;
} 
inline void P_int(int *a,int *ans,int n){
	for(int i=1;i<n;i++) ans[i]=1ll*ksm(i,MOD-2)*a[i-1]%MOD;
	ans[0]=0;
}
inline void P_ln(int *a,int *ans,int n){
	static int inva[maxn<<1],dera[maxn<<1];
	int len=1,cnt=0;
	while(len<(n<<1)) len<<=1,cnt++;
	for(int i=0;i<len;i++) r[i]=(r[i>>1]>>1)|((i&1)<<(cnt-1));
	for(int i=0;i<len;i++) inva[i]=dera[i]=0;
	P_inv(a,inva,n);
	P_der(a,dera,n);
	NTT(inva,len,1); NTT(dera,len,1);
	for(int i=0;i<len;i++) inva[i]=(1ll*inva[i]*dera[i])%MOD;
	NTT(inva,len,-1);
	P_int(inva,ans,n);
	for(int i=n;i<len;i++) ans[i]=0;
}

三、多項式牛頓迭代

已知一個函數\(G(x)\),求一個多項式\(F(x)\),使得:

\[G(F(x))\equiv 0 \pmod{x^n} \]

\(n=1\)時,\(G(F(x))\equiv 0 \pmod{x}\),這個單獨求。

假設我們已經知道了一個多項式\(F_0(x)\),使得:

\[G(F_0(x))\equiv 0 \pmod{x^{\lceil n/2 \rceil}} \]

考慮將\(G(F(x))\)\(F_0(x)\)處泰勒展開:

\[G(F(x))\equiv G(F_0(x))+\frac{G'(F_0(x))}{1!}(F(x)-F_0(x))+\frac{G''(F_0(x))}{2!}(F(x)-F_0(x))^2+... \pmod{x^{\lceil n/2 \rceil}} \]

由於\(F(x)-F_0(x))\)最低的非\(0\)次項次數最低為\(\lceil n/2 \rceil\),所以我們有:

\[G(F(x))\equiv G(F_0(x))+G(F_0(x))(F(x)-F_0(x)) \pmod{x^n} \]

移項,合並同類項,我們有:

\[F(x)\equiv F_0(x)-\frac{G(F_0(x))}{G'(F_0(x))} \pmod{x^n} \]

四、多項式exp

已知一個多項式\(G(x)\),求一個多項式 \(F(x)\),使得:

\[F(x)=e^{G(x)}\pmod{x^n} \]

多項式有exp?

和多項式對數函數一樣,我們也可以把它看作是麥克勞林級數:

\[e^{F(x)} = \sum _{i\ge 0} \frac{F^i(x)}{i!} \]

這里,多項式的常數項必須為0,否則常數項不收斂。

兩邊取自然對數得:

\[\ln F(x) \equiv G(x) \pmod{x^n} \]

移項得:

\[\ln F(x) -G(x) \equiv 0 \pmod{x^n} \]

令函數\(f(F(x))=\ln F(x) -G(x)\),對其進行牛頓迭代,我們就有了遞推式:

\[F(x)\equiv F_0(x) - \frac{f(F_0(x))}{f'(F_0(x))} \pmod{x^n} \]

通過計算得到:

\[f'(F(x))=\frac{1}{F(x)} \]

代入,經過計算,我們便得到:

\[F(x)\equiv F_0(1+G(x)-\ln F_0(x)) \pmod{x^n} \]

時間復雜度

\[T(n)=T(n/2)+O(n \log n) = O(n \log n) \]

核心代碼:

inline void P_exp(int *a,int *ans,int n){
	static int lna[maxn<<1],tmp[maxn<<1];
	if(n==1){
		ans[0]=1;
		return;
	}
	P_exp(a,ans,(n+1)>>1);
	P_ln(ans,lna,n);
	int len=1,cnt=0;
	while(len<(n<<1)) len<<=1,cnt++;
	for(int i=0;i<len;i++) r[i]=(r[i>>1]>>1)|((i&1)<<(cnt-1));
	for(int i=0;i<n;i++) tmp[i]=a[i];
	for(int i=n;i<len;i++) tmp[i]=0;
	NTT(tmp,len,1); NTT(lna,len,1);
	NTT(ans,len,1);
	for(int i=0;i<len;i++) ans[i]=1ll*ans[i]*((1+tmp[i]-lna[i]+MOD)%MOD)%MOD;
	NTT(ans,len,-1);
	for(int i=n;i<len;i++) ans[i]=0;
}

(這看似短短的代碼調了我兩天。。。主要是前面寫的ln和inv中有很多數組清空的不徹底。。。)

五、多項式開根

已知一個多項式\(G(x)\),求一個多項式\(F(x)\),使得:

\[F^2(x)\equiv G(x) \pmod{x^n} \]

移項得:

\[F^2(x)-G(x)\equiv 0\pmod{x^n} \]

令函數\(f(F(x))=F^2(x)-G(x)\),對其進行牛頓迭代,我們就有了遞推式:

\[F(x)\equiv F_0(x) - \frac{f(F_0(x))}{f'(F_0(x))} \pmod{x^n} \]

通過計算得到:

\[f'(F(x))=2F(x) \]

代入,經過計算,我們便得到:

\[F(x)\equiv \frac{F_0^2(x)+G(x)}{2F_0(x)} \pmod{x^n}時間復雜度$O(n \log n)$ \]

將式子變形一下:

\[F(x) \equiv \frac{F_0(x)}{2} + \frac{G(x)}{2F_0(x)} \pmod{x^n} \]

時間復雜度\(O(n \log n)\)

剛剛還看到了一種解法,和大家分享一下:

兩邊同取自然對數:

\[\ln (F^2(x)) \equiv \ln (G(x)) \pmod{x^n} \]

所以

\[\ln (F(x)) \equiv \ln(\frac{G(x)}{2}) \pmod{x^n} \]

所以

\[F(x)\equiv e^{\ln(\frac{G(x)}{2})} \pmod{x^n} \]

牛頓迭代版:

inline void P_sqrt(int *a,int *ans,int n){
	static int tmp[maxn<<1],inva[maxn<<1];
	if(n==1){
		ans[0]=1;
		return;
	}
	P_sqrt(a,ans,(n+1)>>1); 
	int len=1,cnt=0;
	while(len<(n<<1)) len<<=1,cnt++;
	for(int i=0;i<len;i++) r[i]=(r[i>>1]>>1)|((i&1)<<(cnt-1));
	for(int i=0;i<len;i++) inva[i]=0;
	P_inv(ans,inva,n);
	for(int i=0;i<n;i++) tmp[i]=a[i];
	for(int i=n;i<len;i++) tmp[i]=0;
	NTT(tmp,len,1); NTT(inva,len,1);
	for(int i=0;i<len;i++) tmp[i]=(1ll*tmp[i]*inva[i])%MOD;
	NTT(tmp,len,-1);
	for(int i=0;i<len;i++) ans[i]=1ll*(tmp[i]+ans[i])%MOD*inv2%MOD;
	for(int i=n;i<len;i++) ans[i]=0;
}

exp版:

inline void P_sqrt(int *a,int *ans,int n){
	static int lna[maxn<<1];
	P_ln(a,lna,n);
	for(int i=0;i<n;i++) lna[i]=(1ll*lna[i]*inv2)%MOD;
	P_exp(lna,ans,n);
}

然而用exp寫的T了3個點。。。還是老老實實寫牛迭吧。

大家有沒有發現,在上面牛頓迭代的代碼中,我們默認了常數項是1?那么如果常數項不是1呢?

這時候,我們就要計算二次剩余了。

又是一個被我咕了的東西

六、多項式快速冪

已知一個多項式\(G(x)\),求一個多項式\(F(x)\),使得

\[F(x)\equiv G^k(x) \pmod{x^n} \]

其中\(k\in \Q\)

因為

\[G(x)=e^{\ln G(x)} \]

所以

\[F(x)\equiv e^{k \ln G(x)} \pmod{x^n} \]

時間復雜度\(O(n \log n)\)

核心代碼:

inline void P_power(int* a,int *ans,int n){
	static int lna[maxn];
	for(int i=0;i<n;i++) lna[i]=0;
	P_ln(a,lna,n);
	for(int i=0;i<n;i++) lna[i]=(1ll*lna[i]*k)%MOD;
	P_exp(lna,ans,n);
}

在洛谷的模板題里,指數達到了\(10^{10^5}\)級別,那么我們該怎么辦呢?其實很簡單,我們只需將指數對\(MOD\)取模就行了。(這個還是比較顯然的吧)

注意,因為ln的緣故,這里常數項也必須是1。

好了,先將這么多操作,以后我會補充的。

那我們來一道例題吧:

P4091 [HEOI2016/TJOI2016]求和

求:

\[f(n)=\sum_{i=0}^n\sum_{j=0}^iS(i,j)\times2^j\times(j!) \]

其中\(S(i,j)\)為第二類斯特林數

首先我我們呢得知道第二類斯特林數是啥。

\(S(i,j)\)是用來解決LUB問題的(就是把\(i\)個不同的球放入\(j\)個相同的盒子里,且盒子不能為空。有興趣的可以去了解TwelveFoldway)。

我們先考慮盒子不同時怎么做。考慮枚舉空盒的個數,那么我們就可以大力容斥了,最后因為盒子相同,除以\(j!\)即可。於是我們得到:

\[S(i,j)=\frac{\sum_{k=0}^j(-1)^kC_j^k(j-k)^i}{j!} \]

現在我們開始推式子。

改變枚舉順序,將\(2^j\)提取出來,我們得到:

\[f(n)=\sum_{j=0}^n2^j\sum_{i=0}^nS(i,j)\times(j!) \]

(注意當\(i<j\)時,\(S(i,j)=0\))

代入第二類斯特林數的計算公式,我們得到:

\[f(n)=\sum_{j=0}^n2^j\sum_{i=0}^n\sum_{k=0}^j(-1)^kC_j^k(j-k)^i \]

\(k\)的枚舉提前,我們得到:

\[f(n)=\sum_{j=0}^n2^j\sum_{k=0}^j(-1)^kC_j^k\sum_{i=0}^n(j-k)^i \]

我們可以看到,后面兩個sigma相乘,其實可以把他們寫成多項式卷積。

\[A[k]=(-1)^kC_j^k \\ B[k]=\sum_{i=0}^n k^i = \frac{k^{n+1}-1}{k-1} \]

所以我們有:

\[f(n)=\sum^n_{j=0}2^j(A*B) \]

於是我們直接NTT就好了。

我們再來一道題:P3321 [SDOI2015]序列統計

給定整數 \(x\),求所有可以生成出的,且滿足數列中所有數的乘積 \(\bmod \ m\)的值等於 \(x\) 的不同的數列的有多少個。

既然是計數,那么我們考慮\(dp\)

\(dp[i][j]\)表示選了\(i\)個數,數列的積模\(m\)的值為\(j\),那么我們有以下的狀態轉移方程:

\[dp[2*i][c] = \sum_{a*b\equiv c\pmod{m}} dp[i][a]*dp[i][b] \]

我們觀察這個式子,發現如果是\(a+b=c\)的話,我們就完全可以卷積嘛!

那么我們這里就有了一個奇技淫巧:取對數

我們在高一學必修一的時候就學過:(雖然我還沒有高一

\[\log _a n*m=\log_a n + \log _a m \]

注意我們是模意義下的對數,聯想到原根的一些性質(我保證這坑我以后會補),我們只需以原根為底取對數即可

\(A=\log_g a \%m,B=\log _g b\%m,C=\log_g c\%m\),於是式子就被我們改寫成:

\[dp[2*i][C]=\sum_{A+B\equiv C\pmod{\varphi(m)}} dp[i][A]*dp[i][B] \]

好了,我今天就分享到這里了。

如有不足敬請指正,謝謝!

參考資料:

1.pmt巨佬的講義(%%%pmt巨佬)

2.OI wiki

3.Miskcoo's space


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM