實在是太毒瘤了。
大綱。
多項式生成函數相關
默認前置:微積分,各種數和各種反演,FFT,NTT,各種卷積,基本和式變換。
主要內容:
泰勒展開,級數求和,牛頓迭代,主定理。 //例題:在美妙的數學王國中暢游,禮物
多項式全家桶:乘法,求逆,求導,積分,分治,ln,exp,fwt,MTT。 //城市規划,圖的價值,染色,遺失的答案,按位或,隨機游走。
生成函數:普通型生成函數,指數型生成函數計數原理。 //獵人殺,遺忘的集合,生成樹計數
例題。
一、泰勒展開和級數求和
1.泰勒展開
即對於任何函數\(f(x)\),如果在\(x_0\)處\(n\)階可導,那么滿足如下公式:
這里當\(x_0\)為0的時候被稱作麥克勞林公式。
先推導麥克勞林公式
即:
這里只證明多項式函數的正確性(其實是因為任意函數太難證了吧)。
設多項式函數:
那么:
而我們的\(x_0=0\)
也就是說每階導數都只有常數項。
即:
所以
那如果\(x_0!=0\)呢?
這時候就是真正的泰勒展開了。
我們照舊設多項式函數:
將之更換為關於\(w=x-x_0\)的多項式。
用麥克勞林公式展開:
也就是說:$$b_i=\frac{g^{(i)}}{i!}$$
那么:
所以:
這樣的話就是說:
證畢。
2.一些級數求和的公式
其實個人認為泰勒展開是級數求和的逆運算。
二、牛頓迭代
1.牛頓迭代
本來這個東西是用來求連續函數的。
先講一下連續函數是怎么搞得。
對於一個函數\(f(x)\)
我們期望求解\(f(x)=0\)的解\(x_z\)
首先需要一個接近\(x_z\)的值\(x_0\),保證在區間\([x_z,x_0]\)范圍內,\(f^{(2)}(x)\)的符號不變。
而我們期望得到一個更加接近\(x_z\)的解\(x_1\)。
怎么得到呢?
將\(f(x_1)\)在\(x_0\)處泰勒展開。
得到:
我們只保留其線性部分。
個人猜測牛頓這樣做的原因是因為這樣迭代一次之后\(x_1\)僅有一個解,如果保留二次項部分,雖然會讓迭代的次數減少,不過此時有兩個解,剩余部分的迭代增加量變成了指數級別的。
那么也就是說我們需要求:
此時\(x_1=x_0-\frac{f(x_0)}{f'(x_0)}\)
不斷迭代下去就可以不斷逼近解\(x_z\)
考慮為什么。
由於二階導符號不變,那么也就是說函數的凹凸性不變,這樣的話一階導的就是單調的了。
其實就是函數切線的斜率單調,我們求出的\(x_1\)就是切線的解,這樣就可以不斷的逼近\(x_z\)。
2.多項式牛頓迭代
本來這個東西使用來求連續函數的。
現在我們把它搞到離散數學里面來,利用相似的思想。
求解如下方程。
按照套路來。
我們假設已經求出了\(G'(x)\)
滿足:
對函數\(F(G(x))\)在\(G'(x)\)處泰勒展開。
可以發現:
所以:
那么:
這樣也就是說,不准確的部分只有一階導部分。
也就是:
每次迭代精度翻倍了。
可以遞歸求解。
復雜度是:\(T(n)=nlogn+T(\frac{n}{2})=nlogn\)。
可能要疑問這個是干什么用的。
下面的全家桶就知道了。
講個題:https://www.cnblogs.com/Lrefrain/p/11921361.html.
三、主定理
主定理用來證明一些分治遞歸算法的復雜度。
即。
對於如下的復雜度:
設\(f(n)=n^d\)
有:
偷一張算導的圖:
是遞歸示意圖了。
那么可以發現總的復雜度可以這樣來表示:
討論一下。
1.\(a<b^d\)后面那個東西是小數,所以說是收斂的,是一個常數,所以\(T(n)=n^d\)
2.\(a=b^d\)后面全是1,所以\(T(n)=n^dlog_bn\)
3.\(a>b^d\)后面東西,對於最后一項來說,前面的全是常數,所以舍去前面的項。
推一下:
四、多項式全家桶
1.導數\(deriv\)
就和普通導數一樣,對於第\(i\)項,\(A[i]=A[i+1]*(i+1)\)常數項舍去。
2.不定積分\(integ\)
就和普通不定積分一樣,對於第\(i\)項,\(A[i]=\frac{A[i-1]}{i}\)最高次項舍去。
3.求逆\(inv\)
這個稍微推一下。
對於一個多項式\(A(x)\),我們需要的\(B(x)\)是滿足:
假設我們已經求出了一個\(B'(x)\)使得:
那么:
因為他倆前\(\frac{n}{2}\)項一樣。
那么:
也就是:
兩邊同乘\(A(x)\)
這樣我們就得到了一個遞歸算法,使得每次回溯精度翻倍,而在最底層只有常數項,直接費馬小定理即可。
注意這里我們求出的逆可能不僅有\(n\)項,但是我們只保留\(n\)項,這樣雖然不精確,不過更高的項是沒有用的。
4.\(ln\)
對於一個多項式\(A(x)\)(必須保證常數項為1),我們要求的\(B(x)\)是滿足:
兩側求導。
這樣可通過求逆和求導得到\(B'(x)\)
再對\(B'(x)\)不定積分即可得到\(B(x)\)
同樣省略大於\(n\)的高次項。
5.開方\(sqr\)
必須保證\(A(0)=1\)
首先,
對於任何一個多項式運算我們均可以設計一種多項式函數,使得運算結果\(B(x)\)為方程:
的解。
開方就是這樣的。
我們要設計的函數就是:
那么我們要求的就是:
這樣延續套路,假設我們求出了:
我們對\(F(B(x))\)在\(F(B_0(x))\)處泰勒展開。
由於\(B_0(x)\)和\(B(x)\)的前\(x^{\frac{n}{2}}\)項是相同的,那么包括2次及以上的項均為0。
那么只剩下常數項和一階導了。
消序展開得到:
這種方法可以求解任何多項式函數。
也被稱作多項式牛頓迭代。
在開方中就是:
這樣的到了遞歸算法,回溯一次精度翻倍,遞歸到最后一層常數項為1。
6.\(exp\)
對於一個函數\(A(x)\)(常數項為0)。
求:
構造$$F(B(x))=ln(B(x))-A(x)$$
那么要求:
利用牛頓迭代公式:
同樣是遞歸算法,遞歸一次精度翻倍,遞歸到最后一層常數項為1。
7.快速冪
不是直接倍增的。
要求:
這樣我們求個\(ln\),然后給他乘上\(k\),然后再\(exp\)回去即可。
8.分治\(FFT\)
對於這樣一個東西:
求全部的\(f(x)\)
朴素的算法是\(O(n^2)\)的。
這里我們運用\(CDQ\)分治的方法求這個東西,就是\(O(nlog^2n)\)的了,具體實現你們自己去思考。
9.\(MTT\)
我們有時候要求任意模數,這個時候有一種古老的方法是三模數\(NTT\),學長的話說:”這已經是時代的眼淚了“。
現在介紹\(毛TT\),也就是所謂拆系數\(FFT\)
我們怕他爆\(ll\),因為在\(1e5\)卷積的情況下,極限可以到達\(10^{23}\)的地步,所以\(MTT\)出現了。
我們一般設一個常數\(M=1<<15\),接近\(\sqrt{1e9}\)的數。
那么\((f*g)\)其實就被分開了:
這樣我們對於$$A1,A2,B1,B2$$分別做\(4\)次\(FFT\),對於上面三項分別求出,再做\(3\)次\(FFT\)。
這樣每一項的大小都不會超過\(10^{13}\),再分別乘上相對應的\(M\)即可。
雖然乘上\(M\)還是爆了,不過這個時候是可以直接取模的。
另外:
對於如上的全部遞歸算法(求逆,exp,開方)復雜度均為\(O(nlogn)\)
證明:
我們設復雜度為:\(T(n)\)
那么:
根據主定理得到:
給個板子(乘法,求導,積分,求逆,\(ln\),\(exp\),開方,另外一種分治\(FFT\),快速冪)。
int add(int x,int y) {return x+y>=mod?x+y-mod:x+y;}
int mul(int x,int y) {return 1LL*x*y%mod;}
int dic(int x,int y) {return x-y<0?x-y+mod:x-y;}
int qw(int a,int b)
{
int ans=1;
for(;b;b>>=1,a=mul(a,a)) if(b&1) ans=mul(ans,a);
return ans;
}
struct Poly{
int r[maxn],t1[maxn],t2[maxn],t3[maxn],f[maxn],g[maxn],h[maxn],A[maxn],B[maxn],C[maxn];
pair<int,int> getlen(int n) {int mx,tim;for(mx=1,tim=0;mx<=n<<1;mx<<=1,++tim);return make_pair(mx,tim);}
void clear(int *a,int n) {for(int i=0;i<n;++i) a[i]=0;}
void NTT(int mx,int tim,int *a,int op)
{
for(int i=0;i<mx;++i) r[i]=(r[i>>1]>>1)|((i&1)<<tim-1);
for(int i=0;i<mx;++i) if(i<r[i]) swap(a[i],a[r[i]]);
for(int mid=1;mid<mx;mid<<=1)
for(int i=0,len=mid<<1,wn=qw(3,(mod-1)/len);i<mx;i+=len)
for(int j=0,w=1,x=a[i+j],y=mul(w,a[i+mid+j]);j<mid;++j,w=mul(w,wn),x=a[i+j],y=mul(w,a[i+mid+j]))
a[i+j]=add(x,y),a[i+mid+j]=dic(x,y);
if(op==1) return ;
reverse(a+1,a+mx);int re=qw(mx,mod-2);
for(int i=0;i<mx;++i) a[i]=mul(a[i],re);
}
void Mul(int n,int *a,int *b,int *c)
{
pair<int,int> dr=getlen(n);
clear(t3,dr.first);clear(h,dr.first);
for(int i=0;i<n;++i) t3[i]=a[i],h[i]=b[i];
NTT(dr.first,dr.second,t3,1);
NTT(dr.first,dr.second,h,1);
for(int i=0;i<dr.first;++i) c[i]=mul(t3[i],h[i]);
NTT(dr.first,dr.second,c,-1);
for(int i=n;i<dr.first;++i) c[i]=0;
}
vector<int> vecMul(vector<int> a,vector<int> b,int n)
{
pair<int,int> dr=getlen(n);
for(int i=0;i<a.size();++i) t1[i]=a[i];
for(int i=0;i<b.size();++i) t2[i]=b[i];
NTT(dr.first,dr.second,t1,1);
NTT(dr.first,dr.second,t2,1);
for(int i=0;i<dr.first;++i) t1[i]=mul(t1[i],t2[i]);
NTT(dr.first,dr.second,t1,-1);
vector<int> res;
for(int i=0;i<=n;++i) res.push_back(t1[i]);
clear(t1,dr.first);clear(t2,dr.first);
return res;
}
vector<int> DivideMul(int l,int r)
{
if(l==r)
{
vector<int> res;
res.push_back(1),res.push_back(mod-a[l]);
return res;
}
int mid=l+r>>1;
vector<int> fr=DivideMul(l,mid);
vector<int> se=DivideMul(mid+1,r);
return vecMul(fr,se,fr.size()+se.size());
}
void deriv(int n,int *a,int *b) {for(int i=1;i<n;++i) b[i-1]=mul(a[i],i);b[n-1]=0;}
void integ(int n,int *a,int *b) {for(int i=1;i<n;++i) b[i]=mul(a[i-1],qw(i,mod-2));b[0]=0;}
void getinv(int n,int *a,int *b)
{
if(n==1) return b[0]=qw(a[0],mod-2),void();
getinv(n>>1,a,b);
int mx=n<<1,tim=round(log(mx)/log(2));
for(int i=0;i<n;++i) t1[i]=a[i],t2[i]=b[i];
NTT(mx,tim,t1,1);
NTT(mx,tim,t2,1);
for(int i=0;i<mx;++i) t1[i]=mul(t1[i],mul(t2[i],t2[i]));
NTT(mx,tim,t1,-1);
for(int i=0;i<n;++i) b[i]=dic(add(b[i],b[i]),t1[i]);
clear(t1,mx);clear(t2,mx);
}
void getln(int n,int *a,int *b)
{
int mx,tim;
for(mx=1,tim=0;mx<=n<<1;mx<<=1,++tim);
deriv(n,a,f);getinv(n,a,g);
for(int i=0;i<mx;++i) t1[i]=f[i],t2[i]=g[i];
NTT(mx,tim,t1,1);
NTT(mx,tim,t2,1);
for(int i=0;i<mx;++i) t1[i]=mul(t1[i],t2[i]);
NTT(mx,tim,t1,-1);
integ(n,t1,b);
clear(t1,mx);clear(t2,mx);clear(f,mx);clear(g,mx);
}
void getexp(int n,int *a,int *b)
{
if(n==1) return b[0]=1,void();
getexp(n>>1,a,b);
int mx=n<<1,tim=round(log(mx)/log(2));
for(int i=0;i<n;++i) A[i]=b[i];
getln(n,A,B);
for(int i=0;i<n;++i) B[i]=dic(a[i],B[i]);B[0]=add(B[0],1);
NTT(mx,tim,A,1);NTT(mx,tim,B,1);
for(int i=0;i<mx;++i) A[i]=mul(A[i],B[i]);
NTT(mx,tim,A,-1);
for(int i=0;i<n;++i) b[i]=A[i];
clear(A,mx);clear(B,mx);
}
void getsqr(int n,int *a,int *b)
{
if(n==1) return b[0]=1,void();
getsqr(n>>1,a,b);
getinv(n,b,f);
for(int i=0;i<n;++i) g[i]=a[i];
pair<int,int>dr=getlen(n);
NTT(dr.first,dr.second,f,1);NTT(dr.first,dr.second,g,1);
for(int i=0;i<n<<1;++i) f[i]=mul(f[i],g[i]);
NTT(dr.first,dr.second,f,-1);
for(int i=0;i<n;++i) b[i]=mul(add(b[i],f[i]),qw(2,mod-2));
for(int i=0;i<n<<1;++i) f[i]=g[i]=0;
}
void getpow(int n,int k,int *a,int *b)
{
getln(n,a,h);
for(int i=0;i<n;++i) h[i]=mul(h[i],k);
getexp(n,h,b);
}
}fntt;