「總結」多項式生成函數相關(1)


實在是太毒瘤了。

大綱。

多項式生成函數相關

默認前置:微積分,各種數和各種反演,FFT,NTT,各種卷積,基本和式變換。

主要內容:
泰勒展開,級數求和,牛頓迭代,主定理。    //例題:在美妙的數學王國中暢游,禮物
多項式全家桶:乘法,求逆,求導,積分,分治,ln,exp,fwt,MTT。 //城市規划,圖的價值,染色,遺失的答案,按位或,隨機游走。
生成函數:普通型生成函數,指數型生成函數計數原理。 //獵人殺,遺忘的集合,生成樹計數
例題。

一、泰勒展開和級數求和

1.泰勒展開

即對於任何函數\(f(x)\),如果在\(x_0\)\(n\)階可導,那么滿足如下公式:

\[f(x)=\sum\limits_{i=0}^{n}\frac{f^{(i)}(x_0)(x-x_0)^i}{i!} \]

這里當\(x_0\)為0的時候被稱作麥克勞林公式。

先推導麥克勞林公式

即:

\[f(x)=\sum\limits_{i=0}^{n}\frac{f^{(i)}(0)x^i}{i!} \]

這里只證明多項式函數的正確性(其實是因為任意函數太難證了吧)。

設多項式函數:

\[f(x)=\sum\limits_{i=0}^{n}a_ix^i \]

那么:

\[f^{(i)}(x)=\sum\limits_{j=0}^{n}a_{j+i}x^{j}\prod\limits_{k=j+1}^{j+i}k \]

而我們的\(x_0=0\)

也就是說每階導數都只有常數項。

即:

\[f^{(i)}(x)=i!a_i \]

所以

\[f(x)=\sum\limits_{i=0}^{n}\frac{i!a_ix^i}{i!}=\sum\limits_{i=0}^{n}a_ix^i \]

那如果\(x_0!=0\)呢?

這時候就是真正的泰勒展開了。

我們照舊設多項式函數:

\[f(x)=\sum\limits_{i=0}^{n}a_ix^i \]

將之更換為關於\(w=x-x_0\)的多項式。

\[g(w)=f(x)=\sum\limits_{i=0}^{n}b_i(x-x_0)^i \]

用麥克勞林公式展開:

\[g(w)=\sum\limits_{i=0}^{n}\frac{g^{(i)}(0)w^i}{i!} \]

也就是說:$$b_i=\frac{g^{(i)}}{i!}$$

那么:

\[g(w)=f(w+x_0),g^{(i)}(w)=f^{(i)}(w+x_0) \]

\[g(0)=f(x_0),g^{(i)}(0)=f^{(i)}(x_0) \]

所以:

\[b_i=\frac{f^{(i)}(x_0)}{i!} \]

這樣的話就是說:

\[f(x)=\sum\limits_{i=0}^{n}b_i(x-x_0)^i=\sum\limits_{i=0}^{n}\frac{f^{(i)}(x_0)(x-x_0)^i}{i!} \]

證畢。

2.一些級數求和的公式

其實個人認為泰勒展開是級數求和的逆運算。

\[\begin{array}{rcl}f(x)&=&e^x\\&=&\lim\limits_{n\rightarrow +\infty}\sum\limits_{i=0}^{n}\frac{f^{(i)}(0)x^i}{i!}\\&=&\lim\limits_{n\rightarrow +\infty}\sum\limits_{i=0}^{n}\frac{e^0x^i}{i!}\\&=&\lim\limits_{n\rightarrow +\infty}\sum\limits_{i=0}^{n}\frac{x^i}{i!}\end{array} \]

\[\begin{array}{rcl}f(x)&=&\frac{1}{1-x}\\&=&\lim\limits_{n\rightarrow +\infty}\sum\limits_{i=0}^{n}\frac{f^{(i)}(0)x^i}{i!}\\&=&\lim\limits_{n\rightarrow +\infty}\sum\limits_{i=0}^{n}\frac{\frac{i!}{1-0}x^i}{i!}\\&=&\lim\limits_{n\rightarrow +\infty}\sum\limits_{i=0}^{n}x^i\end{array} \]

\[x\in(-1,1) \]

\[\begin{array}{rcl}f(x)&=&\frac{1}{1+x}\\&=&\lim\limits_{n\rightarrow +\infty}\sum\limits_{i=0}^{n}\frac{f^{(i)}(0)x^i}{i!}\\&=&\lim\limits_{n\rightarrow +\infty}\sum\limits_{i=0}^{n}\frac{\frac{(-1)^ii!}{1-0}x^i}{i!}\\&=&\lim\limits_{n\rightarrow +\infty}\sum\limits_{i=0}^{n}(-1)^ix^i\end{array} \]

\[x\in(-1,1) \]

\[\begin{array}{rcl}f(x)&=&sin(x)\\&=&\frac{sin(0)x^0}{0!}+\frac{cos(0)x^1}{1!}+\frac{-sin(0)x^2}{2!}+\frac{-cos(0)x^3}{3!}…\\&=&\lim\limits_{n\rightarrow +\infty}\sum\limits_{i=1}^{n}(-1)^{i-1}\frac{x^{2i-1}}{(2i-1)!}\end{array} \]

\[\begin{array}{rcl}f(x)&=&cos(x)\\&=&\frac{cos(0)x^0}{0!}+\frac{-sin(0)x^1}{1!}+…\\&=&\lim\limits_{n\rightarrow +\infty}\sum\limits_{i=0}^{n}(-1)^i\frac{x^{2i}}{(2i)!}\end{array} \]

\[\begin{array}{rcl}f(x)&=&ln(1+x)\\&=&\frac{ln(1+0)x^0}{0!}+\frac{\frac{1}{1+0}x^1}{1!}+\frac{-\frac{1!}{(1+0)^2}x^2}{2!}+…\\&=&\lim\limits_{n\rightarrow +\infty}\sum\limits_{i=1}^{n}(-1)^{i-1}\frac{x^i}{i}\end{array} \]

\[\begin{aligned} f(x)&=ln(1-x)\\ &=\frac{ln(1-0)x^0}{0!}-\frac{\frac{1}{1-0}x^1}{1!}-\frac{-\frac{1!}{(1-0)^2}x^2}{2!}-...\\ &=\lim\limits_{n\rightarrow+\infty}\sum\limits_{i=1}^{n}-\frac{x^i}{i}\\ \end{aligned}\]

二、牛頓迭代

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\)處泰勒展開。

得到:

\[f(x_1)=\sum\limits_{i=0}^{+\infty}\frac{f^{(i)}(x_0)(x_1-x_0)^i}{i!} \]

我們只保留其線性部分。

個人猜測牛頓這樣做的原因是因為這樣迭代一次之后\(x_1\)僅有一個解,如果保留二次項部分,雖然會讓迭代的次數減少,不過此時有兩個解,剩余部分的迭代增加量變成了指數級別的。

那么也就是說我們需要求:

\[f(x_1)=f(x_0)+f'(x_0)(x_1-x_0)=0 \]

此時\(x_1=x_0-\frac{f(x_0)}{f'(x_0)}\)

不斷迭代下去就可以不斷逼近解\(x_z\)

考慮為什么。

由於二階導符號不變,那么也就是說函數的凹凸性不變,這樣的話一階導的就是單調的了。

其實就是函數切線的斜率單調,我們求出的\(x_1\)就是切線的解,這樣就可以不斷的逼近\(x_z\)

2.多項式牛頓迭代

本來這個東西使用來求連續函數的。

現在我們把它搞到離散數學里面來,利用相似的思想。

求解如下方程。

\[F(G(x))\equiv 0(mod\ x^n) \]

按照套路來。

我們假設已經求出了\(G'(x)\)

滿足:

\[F(G'(x))\equiv 0(mod\ x^{\frac{n}{2}}) \]

對函數\(F(G(x))\)\(G'(x)\)處泰勒展開。

\[F(G(x))=\sum\limits_{i=0}^{+\infty}\frac{F^{(i)}(G'(x))(G(x)-G'(x))^i}{i!} \]

可以發現:

\[G(x)-G'(x)\equiv 0(mod\ x^{\frac{n}{2}}) \]

所以:

\[(G(x)-G'(x))^i\equiv 0(mod\ x^{\frac{ni}{2}}) \]

那么:

\[(G(x)-G'(x))^i\equiv 0(mod\ x^n),i>=2 \]

這樣也就是說,不准確的部分只有一階導部分。

也就是:

\[G(x)=G'(x)-\frac{F(G'(x))}{F'(G'(x))} \]

每次迭代精度翻倍了。

可以遞歸求解。

復雜度是:\(T(n)=nlogn+T(\frac{n}{2})=nlogn\)

可能要疑問這個是干什么用的。

下面的全家桶就知道了。

講個題:https://www.cnblogs.com/Lrefrain/p/11921361.html.

三、主定理

主定理用來證明一些分治遞歸算法的復雜度。

即。

對於如下的復雜度:

\[T(n)=aT(\frac{n}{b})+f(n) \]

\(f(n)=n^d\)

有:

\[T(n)=\begin{cases}n^d&\ a<b^d\\n^dlog_bn&\ a=b^d\\n^{log_ba}&\ a>b^d\\ \end{cases} \]

偷一張算導的圖:

是遞歸示意圖了。

那么可以發現總的復雜度可以這樣來表示:

\[\begin{array}{rcl}T(n)&=&\sum\limits_{i=0}^{log_bn}\left(\frac{n}{b^i}\right)^da^{i}\\&=&n^d\sum\limits_{i=0}^{log_bn}\left(\frac{a}{b^d}\right)^i\end{array} \]

討論一下。

1.\(a<b^d\)后面那個東西是小數,所以說是收斂的,是一個常數,所以\(T(n)=n^d\)

2.\(a=b^d\)后面全是1,所以\(T(n)=n^dlog_bn\)

3.\(a>b^d\)后面東西,對於最后一項來說,前面的全是常數,所以舍去前面的項。

推一下:

\[\begin{array}{rcl}T(n)&=&n^d\left(\frac{a}{b^d}\right)^{log_bn}\\&=&n^d\frac{a^{log_bn}}{b^{dlog_bn}}\\&=&n^d\frac{a^{log_bn}}{n^d}\\&=&n^{log_ba}\end{array} \]

四、多項式全家桶

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)\)是滿足:

\[A(x)B(x)\equiv 1(mod\ x^n) \]

假設我們已經求出了一個\(B'(x)\)使得:

\[A(x)B_0(x)\equiv 1(mod\ x^{\frac{n}{2}}) \]

那么:

\[A(x)(B(x)-B_0(x))\equiv 0(mod\ x^{\frac{n}{2}}) \]

因為他倆前\(\frac{n}{2}\)項一樣。
那么:

\[(B(x)-B_0(x))^2\equiv 0(mod\ x^n) \]

也就是:

\[B^2(x)+B_0^2(x)-2B(x)B_0(x)\equiv 0(mod\ x^n) \]

兩邊同乘\(A(x)\)

\[B(x)+A(x)B_0^2(x)-2B_0(x)\equiv 0(mod\ x^n) \]

\[B(x)\equiv 2B_0(x)-A(x)B_0^2(x)(mod\ x^n) \]

這樣我們就得到了一個遞歸算法,使得每次回溯精度翻倍,而在最底層只有常數項,直接費馬小定理即可。
注意這里我們求出的逆可能不僅有\(n\)項,但是我們只保留\(n\)項,這樣雖然不精確,不過更高的項是沒有用的。

4.\(ln\)
對於一個多項式\(A(x)\)(必須保證常數項為1),我們要求的\(B(x)\)是滿足:

\[ln(A(x))\equiv B(x)(mod\ x^n) \]

兩側求導。

\[B'(x)\equiv \frac{A'(x)}{A(x)}(mod\ x^n) \]

這樣可通過求逆和求導得到\(B'(x)\)
再對\(B'(x)\)不定積分即可得到\(B(x)\)
同樣省略大於\(n\)的高次項。

5.開方\(sqr\)
必須保證\(A(0)=1\)
首先,
對於任何一個多項式運算我們均可以設計一種多項式函數,使得運算結果\(B(x)\)為方程:

\[F(B(x))\equiv 0(mod\ x^n) \]

的解。
開方就是這樣的。
我們要設計的函數就是:

\[F(B(x))=B^2(x)-A(x) \]

那么我們要求的就是:

\[F(B(x))\equiv 0(mod\ x^n) \]

這樣延續套路,假設我們求出了:

\[F(B_0(x))\equiv 0(mod\ x^{\frac{n}{2}}) \]

我們對\(F(B(x))\)\(F(B_0(x))\)處泰勒展開。

\[F(B(x))\equiv \sum\limits_{i=0}^{n}\frac{F^{(i)}(B_0(x))(B(x)-B_0(x))^i}{i!}(mod\ x^n) \]

由於\(B_0(x)\)\(B(x)\)的前\(x^{\frac{n}{2}}\)項是相同的,那么包括2次及以上的項均為0。
那么只剩下常數項和一階導了。

\[F(B(x))\equiv F(B_0(x))+F'(B_0(x))(B(x)-B_0(x))\equiv 0(mod\ x^n) \]

消序展開得到:

\[B(x)\equiv B_0(x)-\frac{F(B_0(x))}{F'(B_0(x))} \]

這種方法可以求解任何多項式函數。
也被稱作多項式牛頓迭代。
在開方中就是:

\[B(x)\equiv \frac{B_0^2(x)+A(x)}{2B_0(x)} \]

這樣的到了遞歸算法,回溯一次精度翻倍,遞歸到最后一層常數項為1。

6.\(exp\)
對於一個函數\(A(x)\)(常數項為0)。
求:

\[B(x)\equiv e^{A(x)}(mod\ x^n) \]

構造$$F(B(x))=ln(B(x))-A(x)$$
那么要求:

\[F(B(x))\equiv 0(mod\ x^n) \]

利用牛頓迭代公式:

\[B(x)\equiv B_0-\frac{F(B_0(x))}{F'(B_0(x))}\equiv B_0(x)(1-ln(B_0(x))+A(x)) \]

同樣是遞歸算法,遞歸一次精度翻倍,遞歸到最后一層常數項為1。

7.快速冪
不是直接倍增的。
要求:

\[B(x)\equiv A^k(x)(mod\ x^n) \]

\[lnB(x)\equiv klnA(x)(mod\ x^n) \]

這樣我們求個\(ln\),然后給他乘上\(k\),然后再\(exp\)回去即可。

8.分治\(FFT\)
對於這樣一個東西:

\[f(n)=\sum\limits_{i=0}^{n-1}f(i)g(n-i) \]

求全部的\(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(x)=\sum\limits_{i=0}^{n}(A1(i)M+B1(i))x^i \]

\[g(x)=\sum\limits_{i=0}^{n}(A2(i)M+B2(i))x^i \]

那么\((f*g)\)其實就被分開了:

\[f_ig_{n-i}=(A1(i)M+B1(i))(A2(i)M+B2(i))=A1(i)A2(i)M^2+(A1(i)B2(i)+A2(i)B1(i))M+B1(i)B2(i) \]

這樣我們對於$$A1,A2,B1,B2$$分別做\(4\)\(FFT\),對於上面三項分別求出,再做\(3\)\(FFT\)
這樣每一項的大小都不會超過\(10^{13}\),再分別乘上相對應的\(M\)即可。
雖然乘上\(M\)還是爆了,不過這個時候是可以直接取模的。

另外:
對於如上的全部遞歸算法(求逆,exp,開方)復雜度均為\(O(nlogn)\)
證明:
我們設復雜度為:\(T(n)\)
那么:

\[T(n)=T(\frac{n}{2})+nlogn \]

根據主定理得到:

\[T(n)=nlogn \]

給個板子(乘法,求導,積分,求逆,\(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;


免責聲明!

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



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