多项式

模板

跳过定义与基础性质与操作。

默认 \(n\)\(2\) 的幂。

\(1.FFT \text{与} NTT\)

设多项式 \(F(x)=\sum_{i=0}f_ix^i\)\(G(x)=\sum_{i=0}g_ix^i\)

首先有多项式卷积 \(H=F \times G\)

那么有 \(h_i=\sum_{j=0}^if_jg_{i-j}\)

直接计算是 \(O(n^2)\) 的。

考虑转化为点值表示,并引入复数

在单位圆上,有单位根 \(\omega_n^k=\cos k\dfrac{2\pi}{n}+\sin k\dfrac{2\pi}{n}i\)

单位根有一些性质:

\[\omega_n^k=\omega_{2n}^{2k} \]

\[\omega_{n}^{k+\frac{n}{2}}=-\omega_n^k \]

\[\omega_n^0=\omega_n^n=1 \]

\((1).DFT\)

我们尝试把多项式 \(F\) 搞成点值表示。

\(F(\omega_n^k)=\sum_{j=0}f_j\omega_n^{kj}\)

首先,将 \(F\) 的各项按照下表奇偶分类:

\[F_1=f_0x^0+f_2x^1+f_4x^2\dots \]

\[F_2=f_1x^0+f_3x^1+f_5x^2\dots \]

于是 \(F(x)=F_1(x^2)+xF_2(x^2)\)

现在我们尝试求出 \(F(\omega_n^k)(k\leq \dfrac{n}{2})\)

代入得 \(F(\omega_n^k)=F_1(\omega_{\frac{n}{2}}^{k})+\omega_n^kF_2(\omega_{\frac{n}{2}}^{k})\)

同时代入 \(\omega_{n}^{k+\frac{n}{2}}\)

\(F(\omega_{n}^{k+\frac{n}{2}})=F_1(\omega_{\frac{n}{2}}^{k})-\omega_n^kF_2(\omega_{\frac{n}{2}}^{k})\)

两式仅有符号不同,可以递归处理。

\(O(nlogn)\)

\((2).IDFT\)

\(F\) 的点值表达为 \(\{(\omega_n^0,y_0),(\omega_n^1,y_1)\dots\}\)

那么有 \(f_i=\dfrac{1}{n}\sum_{j=0}y_j\omega_n^{-ji}\)

我们发现这和 \(F(\omega_n^k)=\sum_{j=0}f_j\omega_n^{kj}\) 长得很像。

于是相似处理。

然后就好了。

最后有一个东西叫蝴蝶操作,可以把递归变成迭代。

\((3).NTT\)

我们发现 \(FFT\) 有精度问题。

对于质数 \(p\) 及其原根 \(g\),有这样一个结论:

\[\omega_n\equiv g^{\frac{p-1}{n}} \pmod p \]

常用的是 \(p=998244353,g=3\)

然后全部换掉就行了。

2.多项式乘法逆

有了多项式乘法,没有除法怎么行呢?

定义:多项式 \(F\) 的乘法逆 \(G\),满足 \(F(x)G(x)=1\)

\((1)\) 递推

由定义得:

\[\sum_{i=0}^{n}f[i]g[n-i]=[n=0] \]

\(n=0\) 时化为 \(f[0]g[0]=1\)

所以 \(g[0]=\dfrac{1}{f[0]}\)

\(n>0\) 时有:

\[\sum_{i=1}^{n}f[i]g[n-i]+f[0]g[n]=0 \]

所以得到\(g[n]=-\dfrac{1}{f[0]}\sum_{i=1}^{n}f[i]g[n-i]\)

于是我们能得到 \(F\) 存在逆的充要条件是 \(f[0]≠0\)

可惜这个方法是 \(O(n^2)\) 的。

\((2)\) 倍增

\(R(x) = F^{-1}(x)\ (mod\ x^{2n}),S(x) = F^{-1}(x)\ (mod\ x^{n})\)

那么有 \(R(x)-S(x)=0\ (mod\ x^n)\)

两边平方:\(R(x)^2-2*R(x)*S(x)+S(x)^2=0\ (mod\ x^{2n})\)

同乘 \(F(x),R(x)-2*S(x)+F(x)S(x)^2=0\ (mod\ x^{2n})\)

所以有 \(R(x)=2*S(x)-F(x)S(x)^2\)

3.导数和积分

由于

\[(x^a)'=ax^{a-1} \]

\[\int x^a=\dfrac{1}{a+1}x^{a+1} \]

对于多项式来说,每一项分别求导/积分,再加起来就行。

所以

\[F'(x)=\sum_{i=0} f_{i+1}(i+1)x^i \]

\[\int F(x)=\sum_{i=1}\dfrac{a_{i-1}}{i}x^i \]

4.对数

\[\ln(F(x))'\equiv \ln'(F(x))F'(x)=\dfrac{F'(x)}{F(x)} \]

求导。求逆。积分。

5. \(\text{exp}\)

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

两边求导得:

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

积回去:

\[G(x)=\int G(x)F'(x) \]

\[[x^n]G(x)=[x^n]\int G(x)F'(x)=\dfrac{1}{n}\sum_{i=0}^{n-1}f_ig_{n-1-i} \]

这长的就很分治 NTT。

5.5

接下来就能做 \(F^k\) 的东西了,包括开根。

不过常数项不为 1 的时候不能直接用板子。

设常数项为 \(c\)

则可以设 \(G=\dfrac{F}{c}\),求 \(G^k=\dfrac{F^k}{c^k}\)

\(F^k=c^kG^k\)

对于 \(k\) 是整数的时候还可以搞,但是比如开根的时候, \(c^k\) 就得是二次剩余了。

另外,当心常数项等于 0,这时候要整体位移以后做。

6.取模

\(n\) 次多项式 \(F\)\(m\) 次多项式 \(G\),要求找到 \(n-m\) 次多项式 \(Q\)\(m-1\) 次多项式 \(R\),使得:

\[F=G \times Q+R \]

设多项式 \(F\) 系数翻转后的多项式为 \(F_R\),则有关系 \(F(\dfrac{1}{x})x^n=F_R(x)\)

那么,

\[F(x)=G(x)\times Q(x)+R(x) \]

\[F(\dfrac{1}{x})=G(\dfrac{1}{x})\times Q(\dfrac{1}{x})+R(\dfrac{1}{x}) \]

\[F(\dfrac{1}{x})x^n=G(\dfrac{1}{x})x^m\times Q(\dfrac{1}{x})x^{n-m}+R(\dfrac{1}{x})x^{m-1} \times x^{n-m+1} \]

\[F_R(x)=G_R(x)\times Q_R(x)+R_R(x) \times x^{n-m+1} \]

\[F_R(x) \equiv G_R(x)\times Q_R(x) \pmod {x^{n-m+1}} \]

\[Q_R(x) \equiv F_R(x)\times G_R(x)^{-1} \pmod {x^{n-m+1}} \]

于是就求出了 \(Q_R\),简单变换可得 \(Q\)

然后简单变换可得 \(R=F-Q\times G\)

6.5 常齐线推

\(f_n=\sum_{k=1}^mc_kf_{n-k}\),给定 \(c_1\dots c_m,f_0\dots f_{m-1}\),求 \(f_N\ (N=10^9)\)

经过一些手膜可以发现,可以通过对 \(F(x)=x^N\) 取模来得到 \(f_N=\sum_{i=0}^{m-1}C_if_i\) 中的系数。

作为模数的多项式是 \(G=\sum_{i=0}^{m-1}-c_{m-i}x^i+x^m\)

具体实现上可以对 \(x^N\) 的计算使用快速幂,只不过将整数取模换成了多项式取模。

复杂度 \(O(m\log m\log n)\)

应用

1.斯特林数全家桶

斯二林行

\(\begin{Bmatrix}n\\m\end{Bmatrix}=\sum_{i=0}^m\dfrac{(-1)^{m-i}}{(m-i)!}\dfrac{i^n}{i!}\),直接卷。

斯二林列

\((e^z-1)^m=m!\sum_{n\ge 0}\begin{Bmatrix}n\\m\end{Bmatrix}\dfrac{z^n}{n!}\)

斯一林行

\(z^{\overline{n}}\)(上升幂)\(=\sum_{m=0}^n\begin{bmatrix}n\\m\end{bmatrix}z^m\),用倍增做。

斯一林列

\((\ln\dfrac{1}{1-z})^m=m!\sum_{n\ge 0}\begin{bmatrix}n\\m\end{bmatrix}\dfrac{z^n}{n!}\)

斯二行求和

就是贝尔数, \(\text{EGF}=\exp(e^z-1)\).

斯二列求和

注意到上面的式子,\(S_n=F\times G_n\),其中 \([z^m]S_n=\begin{Bmatrix}n\\m\end{Bmatrix},[z^m]F=\dfrac{(-1)^m}{m!},[z^m]G_n=\dfrac{m^n}{m!}\).

那么列求和 \(Sum_n=\sum_{i=0}^n S_i=F\times \sum_{i=0}^nG_n\).

后面那个和式的计算十分的显然,直接等比数列求和就好了。(真不知道 P4091 咋评的黑)

斯一行求和

这不是搞笑吗? \(ans=n!\).

斯一行的部分和

\(m!\) 除到左边去,左边变成 \(e^{-\ln(1-x)}\) 的前 \(m\) 项展开,多项式复合一下未尝不可?

斯一列求和

不会。

2.欧拉数

\(\langle{n \atop m}\rangle=\sum_{i=0}^m\dbinom{n+1}{i}(-1)^i(m-i+1)^n\)

又是直接卷。

3.三角函数

\(e^{ix}=\cos(x)+i\sin(x)\).

同理,\(e^{-ix}=\cos(x)-i\sin(x)\).

那么,\(\cos(x)=\dfrac{e^{ix}+e^{-ix}}{2},\sin(x)=\dfrac{e^{ix}-e^{-ix}}{2i}\).

模意义下 \(i\equiv g^{\frac{p-1}{4}} \pmod p\).

一些结论

1.\(\ln(1-x^k)\)

\[F=1-x^k,G=\ln F=\ln(1-x^k) \]

\[G'=\dfrac{F'}{F}=-\dfrac{kx^{k-1}}{1-x^k}=-kx^{k-1}\sum_{i=0}x^{ki}=-k\sum_{i=1}x^{ki-1} \]

\[G=-\sum_{i=0}\dfrac{x^{ik}}{i} \]

2.\(f(x+c)\)(可用于斯一林行的倍增)

\[f(x+c)=\sum_{i=0}^nf_i\sum_{j=0}^i\dbinom{i}{j}x^jc^{i-j} \]

\[=\sum_{j=0}^nx^j\sum_{i=j}^nf_i\dbinom{i}{j} c^{i-j} \]

\[=\sum_{j=0}^n\dfrac{x^j}{j!}\sum_{i=j}^nf_i\times i! \dfrac{c^{i-j}}{(i-j)!} \]

\(F=\sum_{i=0}^nf_{n-i}(n-i)!x^i,G=\sum_{i=0}^n\dfrac{c^i}{i!}x^i\)

那么就有 \(f(x+c)=\sum_{i=0}^n[x^{n-i}](F\times G)\dfrac{x^i}{i!}\)

3.\(\sum_{i=0}^{m}i^n\)

1. 伯努利数

伯努利数的 \(\text{EGF}\)\(B=\dfrac{x}{e^x-1}\)。用它求自然数幂和的式子是:

\[\sum_{i=0}^{n}i^k=\dfrac{1}{k+1}\sum_{i=0}^k\binom{k+1}{i}B_i\times n^{k+1-i} \]

展开组合数后可以轻松卷积。

2. \(\text{EGF}\)

直接求 \(\sum_{i=0}^ni^k\)\(\text{EGF}\)

\[\begin{aligned}F&=\sum_{k \ge 0}\sum_{i=0}^ni^k\dfrac{x^k}{k!}\\&=\sum_{i=0}^n\sum_{k \ge 0}\dfrac{(ix)^k}{k!}\\&=\sum_{i=0}^ne^{ix}\\&=\dfrac{1-e^{nx}}{1-e^x}\end{aligned} \]


多项式模板(更新至 取模):

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define f(i,a,b) for(int i=a;i<=b;++i)
#define wt int tt=d;while(tt--)
#define py puts("Yes")
#define pn puts("No")
#define fe(i,e) for(int i=0;i<e.size();i++)
#define vi vector<ll>
inline ll rd() {
	ll x=0,f=1;
	char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c))x=x*10+c-'0',c=getchar();
	return x*f;
}
ll dx[4]={0,1,0,-1};
ll dy[4]={1,0,-1,0};
#define d rd()
#define pb push_back
const ll Bit=21;
const ll PoL=(1<<Bit)+5;
ll but[PoL],pw[2][PoL],Curl=-1;
void out(ll *a,ll n){f(i,0,n)printf("%lld ",a[i]);puts("");}
const ll mod=998244353,G=3,Gi=332748118;
ll qp(ll a,ll b=mod-2){ll ans=1;
	while(b){
		if(b&1)ans=ans*a%mod;
		a=a*a%mod;b>>=1;
	}return ans%mod;
}
namespace poly{
	ll jc[PoL+10],inv[PoL+10],inc[PoL+10];
	ll w[PoL]={1},qwq=0,Curl=-1;
	// ll usla[PoL],uslb[PoL];
	bool hpre=0;
	void pre(){
		if(hpre)return;hpre=1;
		jc[0]=jc[1]=inc[0]=inc[1]=inv[0]=inv[1]=1;
		f(i,2,PoL-1)jc[i]=jc[i-1]*i%mod,inv[i]=(mod-mod/i)*inv[mod%i]%mod,
		inc[i]=inc[i-1]*inv[i]%mod;
	}
	#define ck(x) ((x)>=mod?(x)-mod:(x))
	void revbit(ll k){
		if(Curl==k)return;Curl=k;
		f(i,0,k-1)but[i]=but[i>>1]>>1|((i&1)?(k>>1):0);
	}
	ll NTT(ll *a,ll *to,ll n,ll o){
		ll _n=1;while(_n<=n)_n<<=1;n=_n;
		revbit(n);qwq=0;
		ll inv=qp(n);
		
		// memcpy(to,a,sizeof(to));
		f(i,0,n-1)to[i]=a[i];
		f(i,0,n-1)if(i<but[i])swap(to[i],to[but[i]]);
		
		for(int l=1;l<n;l<<=1){qwq++;
			ll Wn=qp((o==1?G:Gi),((mod-1)/(l<<1)));
			for(int i=1;i<l;i++)w[i]=w[i-1]*Wn%mod;
			for(int j=0;j<n;j+=l+l){
				for(int i=0;i<l;i++){
					int tt=w[i]*to[i|j|l]%mod;
					to[i|j|l]=mod+to[i|j]-tt;
					to[i|j]=to[i|j]+tt;
				}
			}if(qwq%9==0)f(i,0,n-1)to[i]%=mod;
		}if(o==1)inv=1;
		f(i,0,n-1)to[i]=to[i]*inv%mod;
		return n-1;
	}
	void Mul(ll *f,ll *g,ll *to,ll n,ll m){
		static ll _f[PoL],_g[PoL],_n,_m;_n=n,_m=m;
		f(i,0,_n)_f[i]=f[i];
		f(i,0,_m)_g[i]=g[i];
		_m+=_n,_n=1;
		_n=NTT(_f,_f,_m,1);NTT(_g,_g,_m,1);
		f(i,0,_n)to[i]=_f[i]*_g[i]%mod;
		NTT(to,to,_m,-1);
		f(i,_m+1,_n)to[i]=0;
		f(i,0,_n)_f[i]=_g[i]=0;
	}
	void Inv(ll *a,ll *to,ll *usl,ll n){//a!=to
		ll k=1;to[0]=qp(a[0]);
		while(k<=n){k<<=1;
			f(i,0,k-1)usl[i]=a[i];
			NTT(to,to,k*2-1,1),NTT(usl,usl,k*2-1,1);
			f(i,0,k*2-1)to[i]=(2-usl[i]*to[i]%mod+mod)%mod*to[i]%mod;
			NTT(to,to,k*2-1,-1);f(i,k,k*2-1)to[i]=0;
		}
	}
	void Dev(ll *a,ll *to,ll n){
		f(i,0,n-1)to[i]=a[i+1]*(i+1)%mod;to[n]=0;
	}
	void Int(ll *a,ll *to,ll n){pre();
		for(int i=n+1;i>=1;i--)to[i]=a[i-1]*inv[i]%mod;
		to[0]=0;//to[0]=原函数的常数项
	}
	void Ln(ll *a,ll *to,ll *usl,ll n){//a!=to
		f(i,1,4*n)usl[i]=0;
		Inv(a,to,usl,n);
		f(i,0,n)usl[i]=a[i];Dev(a,a,n);
		ll len=NTT(to,to,2*n,1);NTT(a,a,2*n,1);
		f(i,0,len)to[i]=to[i]*a[i]%mod;
		NTT(to,to,2*n,-1);f(i,0,n)a[i]=usl[i];
		Int(to,to,len);
		f(i,1,4*n)usl[i]=0;
	}
	void work(ll *a,ll *to,ll *f,ll *g,ll l,ll r){
		if(l==r){
			to[l]=to[l]*inv[l]%mod;
			return;
		}ll mid=l+r>>1;
		work(a,to,f,g,l,mid);ll lf=-1,lg=-1;
		f(i,l,mid)f[++lf]=to[i];f(i,0,r-1-l)g[++lg]=a[i];
		ll len=NTT(f,f,lf+lg,1);NTT(g,g,lf+lg,1);
		f(i,0,len)f[i]=f[i]*g[i]%mod;
		NTT(f,f,lf+lg,-1);
		f(i,mid+1,r)to[i]=(to[i]+f[i-1-l])%mod;
		f(i,0,len)f[i]=g[i]=0;
		work(a,to,f,g,mid+1,r);
	}void exp(ll *a,ll *to,ll *f,ll *g,ll n){//a!=to
		f(i,1,4*n)f[i]=g[i]=0;
		pre();
		Dev(a,a,n);to[0]=1;
		work(a,to,f,g,0,n);
	}
	ll lshift(ll *a,ll n,ll t){//a[t]->a[0]
		f(i,0,n-t)a[i]=a[i+t];
		return n-t;
	}ll rshift(ll *a,ll n,ll t){//a[0]->a[t]
		for(int i=n+t;i>=t;i--)a[i]=a[i-t];
		f(i,0,t-1)a[i]=0;
		return n+t;
	}void mul(ll *a,ll n,ll k){
		f(i,0,n)a[i]=a[i]*k%mod;
	}
	void pwr(ll *a,ll *to,ll *f,ll *g,ll *h,ll n,ll m){//a!=to
		f(i,1,4*n)to[i]=f[i]=g[i]=h[i]=0;
		ll t=0;
		if(a[0]==0){
			f(i,0,n)if(a[i]!=0){t=i;break;}
			if(t==0||m*t>n)return;
		}n=lshift(a,n,t);
		ll C=a[0],iv=qp(C);
		mul(a,n,iv);Ln(a,h,f,n);
		mul(h,n,m);exp(h,to,f,g,n);
		iv=qp(C,m);mul(to,n,iv);
		n=rshift(to,n,t*m);n=n-t*m+t;
	}
	void div(ll *f,ll *g,ll *gr,ll *gg,ll *ff,ll *Q,ll *R,ll n,ll m){
		f(i,0,4*n)gr[i]=ff[i]=0;
		reverse(f,f+n+1);reverse(g,g+m+1);
		Inv(g,gr,ff,n-m+1);
		ll len=NTT(f,ff,2*n,1);NTT(gr,gg,len,1);
		f(i,0,len)ff[i]=ff[i]*gg[i]%mod;
		NTT(ff,Q,len,-1);reverse(Q,Q+n-m+1);
		f(i,n-m+1,len)Q[i]=0;
		reverse(g,g+m+1);
		len=NTT(Q,ff,n*2,1);NTT(g,gr,n*2,1);
		f(i,0,len)ff[i]=ff[i]*gr[i]%mod;
		NTT(ff,ff,len,-1);
		reverse(f,f+n+1);
		f(i,0,n)R[i]=(f[i]-ff[i]+mod)%mod;
	}
}
using namespace poly;
ll f[PoL],g[PoL],ff[PoL],gg[PoL],hh[PoL];
ll n,m,mm,lim;
int main(){
	return 0;
}



posted @ 2022-08-29 21:46  jimmyywang  阅读(75)  评论(2)    收藏  举报