多項式與生成函數學習筆記
多項式
記號
為了方便描述,用大寫字母表示一個多項式,用其對應的小寫字母表示該多項式某一項的系數:
求導
只有我傻傻不會求導,所以放幾個常見導數。
鏈式法則:
泰勒展開
把 \(F(x)\) 在 \(F(x_0)\) 處泰勒展開:
牛頓迭代
給定函數 \(G\) ,找出多項式 \(F(x)\) 使得:
考慮倍增,已知 \(G(F_n(x))\equiv 0 \pmod{x^n}\) ,求 \(F_{2n}(x)\) 滿足 \(G(F_{2n}(x))\equiv 0\pmod{x^{2n}}\) 。
把 \(G(F_{2n}(x))\) 在 \(G(F_{n}(x))\) 處做泰勒展開:
后面的項和左邊的項在 \(\pmod {x^{2n}}\) 意義下都是 \(0\) :
多項式求逆
給定 \(F(x)\) ,求出 \(P(x)\) 滿足 \(F(x)P(x)\equiv 1 \pmod {x^n}\) 。
考慮倍增,設 \(F_n(x)P(x)\equiv 1\pmod{x^n}\) ,那么:
時間復雜度: \(\mathcal O(n\log_2n)\) 。
暴力
時間復雜度: \(\mathcal O(n^2)\) 。
多項式除法
給定 \(A(x),B(x)\) ,求 \(D(x),R(x)\) 滿足 \(A(x)=D(x)B(x)+R(x)\) 並且 \(\deg(R(x))<\deg(B(x))\) 。
設 \(\deg(A(x))=n,\deg(B(x))=m\) ,則 \(\deg(D(x))=n-m,\deg(R(x))<m\) ,不妨設 \(\deg(R(x))=m-1\) 。
記 \(F^R(x)=x^{\deg(F(x))}F(\frac{1}{x})\) ,也就是將 \(F\) 的系數反轉得到的多項式。
原式也可以寫成:
因為 \(\deg(D(x))=n-m<n-m+1\) ,所以求出來的 \(D(x)\) 是真實的,由此可以求出 \(R(x)\) 。
時間復雜度: \(\mathcal O(n\log_2n)\) 。
多項式 ln
給定 \(F(x)\) ,求出 \(P(x)\) 滿足 \(P(x)\equiv \ln(F(x))\pmod{x^n}\) 。
兩邊同時求導:
\(P(x)\) 常數項為 \(0\) , \(F(x)\) 常數項為 \(1\) 。(不為 \(1\) 不收斂)
時間復雜度: \(\mathcal O(n\log_2 n)\) 。
暴力
時間復雜度: \(\mathcal O(n^2)\) 。
多項式 exp
給定 \(F(x)\) ,求出 \(P(x)\) 滿足 \(P(x)\equiv e^{F(x)}\pmod{x^n}\) 。
套牛頓迭代, \(G(X)=\ln X-F(x)\) 。
\(F(x)\) 常數項為 \(0\) , \(P(x)\) 常數項為 \(1\) 。
時間復雜度: \(\mathcal O(n\log_2n)\) 。
暴力
時間復雜度: \(\mathcal O(n^2)\) 。
多項式快速冪
給定 \(F(x)\) ,求出 \(P(x)\) 滿足 \(P(x)\equiv F^m(x) \pmod{x^n}\) 。
兩邊同時取 \(\ln\) :
先多項式取 \(\ln\) ,再乘上 \(m\) ,再 \(\exp\) 回去就行了,需要注意的就是在執行 \(\ln\) 操作時要求 \(F(x)\) 常數項為 \(1\) ,所以先給 \(F(x)\) 除上其常數項的值再進行后面操作,最后再乘上其常數項值的 \(m\) 次方即可還原回去;如果常數項為 \(0\) ,就先把 \(0\) 丟掉(整體除以 \(x\) ),到最后面再加上去(整體乘以 \(x^m\) )即可。
時間復雜度: \(\mathcal O(n\log_2n)\) 。
多項式多點求值
不會,這個考場上考了就隨它去吧。
多項式多點插值
不會,這個考場上考了就隨它去吧。
生成函數
形式冪級數
這里可能不太嚴謹,不過能理解就行了。
對於形式冪級數 \(F(x)=\sum_{i=0}^{\infty}a_ix^i\) ,其中我們並不關心 \(x\) 的數值,且對收斂和發散的問題不感興趣,只對系數序列 \(\{a\}\) 感興趣,只關心 \(\{a\}\) 的值;當數列 \(\{a\}\) 有限的時候,也就是存在 \(t\) ,滿足 \(a_i(i\ge t)=0\) ,那么 \(F(x)\) 也可以稱為形式多項式。
比如我們就可以認為 \(\sum_{i=0}^{\infty}x^i=\frac{1}{1-x}\) ,當然這個式子在 \(x\ge 1\) 是不成立,但是我們並不關心 \(x\) 的具體取值,所以就可以默認 \(0<x<1\) ,然后這樣認為。
生成函數就是形式冪級數。
形式冪級數的運算
設 \(A(x)=\sum_{i=0}^{\infty}a_ix^i,B(x)=\sum_{i=0}^{\infty}b_ix^i\) ,有:
乘積就是乘法卷積。
普通型生成函數
序列 \(\{a\}\) 的普通型生成函數為:
可以用來解決數列遞推等問題。
指數型生成函數
序列 \(\{a\}\) 的指數型生成函數為:
可以用來解決組合排列等問題。
題目
為了假裝自己學了生成函數和多項式,在這里放兩道題目來分析一下。
洛谷P5860 「SWTR-03」Counting Trees
題意簡述
給定長度為 \(n\) 的序列 \(v_i\) ,詢問 \(\{v\}\) 中有多少子序列滿足分別以子序列中的數作為度數的樹存在。
\(2\le n\le 5\times 10^5,1\le v_i\le n\) 。
題目分析
設選出來了一個長度為 \(m\) 的子序列 \(\{a\}\) ,那么需要滿足 \(\sum_{i=1}^ma_i=2(m-1)\) ,即 \(\sum_{i=1}^m(a_i-2)=-2\) ,由此我們可以得出,答案就是 \([x^{-2}]\prod_{i=1}^n(x^{v_i-2}+1)\) 。
但是我們沒有辦法處理 \(x\) 的指數為負的情況,考慮到當 \(v_i=1\) 時, \(x^{v_i-2}+1=\frac{1}{x}+1\) ,可以乘以 \(x\) 使得指數變成正數,當然前面的 \(x^{-2}\) 也要相應地乘以 \(x\) 。
如何求 \(\prod_{i=1}^n(x^{t_i}+1)\) ?這個和用多項式優化背包時一樣的,設 \(F_t(x)=x^{t}+1\) ,那么要求的就是 \(\prod_{i=1}^nF_{t_i}(x)=e^{\sum_{i=1}^n\ln(F_{t_i}(x))}\) ,於是如果知道了 \(\ln(F_t(x))\) ,就可以先算和,最后只需要一次 \(\exp()\) 就可以還原回去了。
如何算 \(\ln(F_t(x))=\ln(x^t+1)\) ?設 \(G(x)=\ln(x^t+1)\) ,那么兩邊同時求導:
對於每個 \(t\) 統計 \(F_t(x)\) 的出現次數然后最后再加起來即可做到 \(n\log_2n\) 。
需要注意的一點就是當 \(v_i=2\) 時 , \(x^{v_i-2}+1=2\) ,需要特殊處理,在 \(\exp()\) 之前先不要乘起來,最后算答案的時候再乘起來。
參考代碼
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ch() getchar()
#define pc(x) putchar(x)
using namespace std;
template<typename T>void read(T&x){
char c;int f;
for(c=ch(),f=1;c<'0'||c>'9';c=ch())if(c=='-')f=-f;
for(x=0;c>='0'&&c<='9';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>void write(T x){
static char q[64];int cnt=0;
if(x==0)return pc('0'),void();
if(x<0)pc('-'),x=-x;
while(x)q[cnt++]=x%10,x/=10;
while(cnt--)pc(q[cnt]+'0');
}
const int maxn=800005,mod=998244353;
int mo(const int x){
return x>=mod?x-mod:x;
}
int power(int a,int x){
int re=1;
while(x){
if(x&1)re=1ll*re*a%mod;
a=1ll*a*a%mod,x>>=1;
}
return re;
}
namespace polynomial{
int power(int a,int x){
int re=1;
while(x){
if(x&1)re=1ll*re*a%mod;
a=1ll*a*a%mod,x>>=1;
}
return re;
}
const int g_=3;
int rev[maxn];
void initNTT(int m,int&n){
n=1;int cn=-1;
while(n<m)n<<=1,++cn;
for(int i=1;i<n;++i)
rev[i]=(rev[i>>1]>>1)|((i&1)<<cn);
}
void NTT(int*F,int n,int rv){
for(int i=0;i<n;++i)if(rev[i]<i)
F[i]^=F[rev[i]]^=F[i]^=F[rev[i]];
for(int mid=1;mid<n;mid<<=1){
const int len=mid<<1,gn=power(g_,(mod-1)/len);
for(int i=0;i<n;i+=len){
for(int j=0,g=1;j<mid;++j,g=1ll*g*gn%mod){
int l=i+j,r=l+mid;
int L=F[l],R=1ll*F[r]*g%mod;
F[l]=mo(L+R),F[r]=mo(mod-R+L);
}
}
}
if(!rv)return;reverse(F+1,F+n);int I=power(n,mod-2);
for(int i=0;i<n;++i)F[i]=1ll*F[i]*I%mod;
}
int Inv[maxn];
void inv(int*F,int*G,int n){
//G(x)=inv(F(x)) mod x^n;
if(n==1)return G[0]=power(F[0],mod-2),void();
inv(F,G,(n+1)>>1);for(int i=(n+1)>>1;i<n;++i)G[i]=0;
int cp=n;initNTT((n+1)*2,n);
for(int i=0;i<cp;++i)Inv[i]=F[i];for(int i=cp;i<n;++i)Inv[i]=G[i]=0;
NTT(Inv,n,0);NTT(G,n,0);for(int i=0;i<n;++i)
G[i]=1ll*G[i]*mo(mod-1ll*G[i]*Inv[i]%mod+2)%mod;
NTT(G,n,1);for(int i=cp;i<n;++i)G[i]=0;
}
int Ln[maxn];
void ln(int*F,int*G,int n){
//G(x)=ln(F(x)) mod x^n;
for(int i=0;i<n;++i)Ln[i]=0;
inv(F,Ln,n);int cp=n;
initNTT((n+1)*2,n);
for(int i=0;i<cp;++i)G[i]=1ll*F[i+1]*(i+1)%mod;
for(int i=cp;i<n;++i)G[i]=Ln[i]=0;NTT(G,n,0);NTT(Ln,n,0);
for(int i=0;i<n;++i)G[i]=1ll*G[i]*Ln[i]%mod;NTT(G,n,1);
for(int i=cp-1;i>=1;--i)G[i]=1ll*G[i-1]*power(i,mod-2)%mod;
G[0]=0;for(int i=cp;i<n;++i)G[i]=0;
}
int Exp[maxn];
void exp(int*F,int*G,int n){
//G(x)=exp(F(x)) mod x^n;
if(n==1)return G[0]=1,void();exp(F,G,(n+1)>>1);
for(int i=(n+1)>>1;i<n;++i)G[i]=0;
for(int i=0;i<n;++i)Exp[i]=0;ln(G,Exp,n);
for(int i=0;i<n;++i)Exp[i]=mo(mod-Exp[i]+F[i]+(i==0));
int cp=n;initNTT((n+1)*2,n);
for(int i=cp;i<n;++i)Exp[i]=G[i]=0;NTT(G,n,0);NTT(Exp,n,0);
for(int i=0;i<n;++i)G[i]=1ll*G[i]*Exp[i]%mod;
NTT(G,n,1);for(int i=cp;i<n;++i)G[i]=0;
}
int Power[maxn];
void power(int*F,int*G,int n,int m0,int m1,int m2){
//There is something wrong with the code.However, I haven't find out where is the problem.
//G(x)=power(F(x),m) mod x^n;
//m0=m%mod m1=m%(mod-1) m2=stand for m
if(m0==0){for(int i=0;i<n;++i)G[i]=(i==0);return;}
int no=0;while(no<n&&F[no]==0)++no;
if(1ll*no*m2>=n){for(int i=0;i<n;++i)G[i]=0;return;}
int V=F[no],I=power(V,mod-2);
for(int i=0;i+no<n;++i)G[i]=1ll*F[i+no]*I%mod,Power[i]=0;
for(int i=n-no;i<n;++i)G[i]=Power[i]=0;ln(G,Power,n);
for(int i=0;i<n;++i)Power[i]=1ll*Power[i]*m0%mod;
exp(Power,G,n);V=power(V,m1);no*=m2;
for(int i=n-1;i>=no;--i)G[i]=1ll*G[i-no]*V%mod;
for(int i=no-1;i>=0;--i)G[i]=0;
}
int Divide0[maxn],Divide1[maxn];
void divide(int*F,int*G,int*Q,int*R,int n,int m){
//F(x)=Q(x)G(x)+R(x) Q(x)? R(x)? deg(F)=n-1 deg(G)=m-1
int tn=n-m+1;
for(int i=0;i<tn;++i)Divide1[i]=(m>=i+1?G[m-i-1]:0);
inv(Divide1,Divide0,tn);
for(int i=0;i<tn;++i)Divide1[i]=(n>=i+1?F[n-i-1]:0);
int cp=tn;initNTT((n+1)*2,tn);
NTT(Divide0,tn,0);NTT(Divide1,tn,0);
for(int i=0;i<tn;++i)Q[i]=1ll*Divide0[i]*Divide1[i]%mod;
NTT(Q,tn,1);for(int i=cp;i<tn;++i)Q[i]=0;reverse(Q,Q+cp);
for(int i=0;i<tn;++i)Divide0[i]=Q[i],Divide1[i]=G[i];
NTT(Divide0,tn,0);NTT(Divide1,tn,0);
for(int i=0;i<tn;++i)R[i]=1ll*Divide0[i]*Divide1[i]%mod;
NTT(R,tn,1);for(int i=0;i<tn;++i)R[i]=mo(mod-R[i]+F[i]);
}
}
int F[maxn],sum[maxn],G[maxn],inv[maxn];
int main(){
int n,cnt1=0,cnt2=0;read(n);
for(int i=1;i<=n;++i){
int v;read(v);
if(v==1){
++cnt1;
++sum[1];
}
else if(v==2){
++cnt2;
}
else{
++sum[v-2];
}
}
if(cnt1<2)puts("0");
else{
cnt1-=2;
inv[1]=1;
for(int i=2;i<=cnt1;++i)
inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
for(int i=1;i<=cnt1;++i)
for(int j=1;i*j<=cnt1;++j)
if((j-1)&1)F[i*j]=mo(mod-1ll*sum[i]*inv[j]%mod+F[i*j]);
else F[i*j]=mo(1ll*sum[i]*inv[j]%mod+F[i*j]);
polynomial::exp(F,G,cnt1+1);
write(1ll*G[cnt1]*power(2,cnt2)%mod);pc('\n');
}
return 0;
}
/*
所以,永遠不要問喪鍾為誰而鳴,它為你而鳴。
*/
CF438E The Child and Binary Tree
題意簡述
正整數集 \(S\) 里面的元素分別為 \(c_1,c_2,\dots,c_n\) ,對於每個 \(1\le i\le m\) ,詢問有多少二叉樹滿足每個點的點權的值屬於 \(S\) ,並且點權和為 \(i\) ,兩棵二叉樹不同當且僅當存在一個點點權不同或者存在一個點的左子樹或右子樹不同。
\(1\le n,m,c_i\le 10^5\) 。
題目分析
設 \(f_x\) 表示點權和為 \(x\) 的二叉樹個數,默認 \(f_0=1\) ,那么有:
設 \(F(x)=\sum_{i=0}^{\infty}f_ix^i\) ,那么有:
設 \(H(x)=\sum_{i=1}^nx^{c_i}\) ,那么:
套牛頓迭代,可以得到:
參考代碼
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ch() getchar()
#define pc(x) putchar(x)
using namespace std;
template<typename T>void read(T&x){
static char c;static int f;
for(c=ch(),f=1;c<'0'||c>'9';c=ch())if(c=='-')f=-f;
for(x=0;c>='0'&&c<='9';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>void write(T x){
static char q[65];int cnt=0;
if(x<0)pc('-'),x=-x;
q[++cnt]=x%10,x/=10;
while(x)
q[++cnt]=x%10,x/=10;
while(cnt)pc(q[cnt--]+'0');
}
const int maxn=800005,mod=998244353,g_=3;
int mo(const int x){
return x>=mod?x-mod:x;
}
int power(int a,int x){
int re=1;
while(x){
if(x&1)re=1ll*re*a%mod;
a=1ll*a*a%mod,x>>=1;
}
return re;
}
int rev[maxn];
void initNTT(int m,int&n){
n=1;int cn=-1;
while(n<=m)n<<=1,++cn;
for(int i=0;i<n;++i)
rev[i]=(rev[i>>1]>>1)|((i&1)<<cn);
}
void NTT(int*F,int n,int rv){
for(int i=0;i<n;++i)if(rev[i]<i)
F[rev[i]]^=F[i]^=F[rev[i]]^=F[i];
for(int mid=1;mid<n;mid<<=1){
const int len=mid<<1,gn=power(g_,(mod-1)/len);
for(int i=0;i<n;i+=len){
for(int j=0,g=1;j<mid;++j,g=1ll*g*gn%mod){
int l=i+j,r=l+mid;
int L=F[l],R=1ll*F[r]*g%mod;
F[l]=mo(L+R),F[r]=mo(mod-R+L);
}
}
}
if(!rv)return;reverse(F+1,F+n);int I=power(n,mod-2);
for(int i=0;i<n;++i)F[i]=1ll*F[i]*I%mod;
}
int Inv[maxn];
void inv(const int*F,int*G,int n){
if(n==1)return G[0]=power(F[0],mod-2),void();
int on=(n+1)>>1;inv(F,G,on);for(int i=on;i<n;++i)G[i]=0;
int cp=n;initNTT((n+1)*2,n);for(int i=0;i<cp;++i)Inv[i]=F[i];
for(int i=cp;i<n;++i)Inv[i]=G[i]=0;NTT(Inv,n,0);NTT(G,n,0);
for(int i=0;i<n;++i)G[i]=1ll*G[i]*mo(mod-1ll*G[i]*Inv[i]%mod+2)%mod;
NTT(G,n,1);for(int i=cp;i<n;++i)G[i]=0;
}
int S0[maxn],S1[maxn];
void solve(const int*F,int*G,int n){
if(n==1)return G[0]=1,void();
int on=(n+1)>>1;solve(F,G,on);for(int i=on;i<n;++i)G[i]=0;
int cp=n;initNTT((n+1)*4,n);for(int i=0;i<cp;++i)S0[i]=F[i];
for(int i=cp;i<n;++i)S0[i]=G[i]=0;NTT(G,n,0);NTT(S0,n,0);
for(int i=0;i<n;++i)S0[i]=mo(mod-1+2ll*S0[i]*G[i]%mod);
NTT(S0,n,1);inv(S0,S1,n=cp);initNTT((n+1)*4,n);for(int i=cp;i<n;++i)S1[i]=0;NTT(S1,n,0);
for(int i=0;i<n;++i)G[i]=1ll*(mod+1)/2*mo(G[i]+1ll*mo(mod-2+G[i])*S1[i]%mod)%mod;
NTT(G,n,1);for(int i=cp;i<n;++i)G[i]=0;
}
int F[maxn],G[maxn];
int main(){
int n,m;read(n),read(m);
while(n--){int c;read(c);F[c]=mo(F[c]+1);}
solve(F,G,m+1);for(int i=1;i<=m;++i)write(G[i]),pc('\n');
return 0;
}
小結
事實上,自己的多項式和生成函數還是搞得太淺了,沒有深入,不過就先入個門吧,以后有時間了再繼續搞一下。