【数学】简单的多项式技巧汇总

【数学】简单的多项式技巧汇总

下面对一些多项式常见操作进行总结

前置芝士

快速数论变换NTT

约定NTT前对于一定长度的范围处理和rev数组初始化函数为getrev()

inline void getrev(int len)
{
    tt = 1,tw = 0;
    while(tt <= len) tt <<= 1,tw++;
    rev[0] = 0;
    for(int i = 1;i < tt;i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (tw - 1));
}

多项式求逆

给定n1次多项式F(x),求G(x)使得F(x)G(x)1 (mod xn)。系数对998244353取模。

考虑倍增法,假设已经有一个函数H(x),F(x)H(x)1 (mod xn2)。我们又知道,F(x)G(x)1 (mod xn2)

作差得:

F(x)(G(x)H(x))0 (mod xn2)

假定F不为0,则有:

G(x)H(x)0 (mod xn2)

平方得:

G2(x)2G(x)H(x)+H2(x)0 (mod xn)

同时乘上F(x):

G(x)2H(x)+H2(x)F(x)0 (mod xn)

G(x)2H(x)+F(x)H2(x) (mod xn)

递归进行计算即可,边界值为G(0)=F(0)MOD2

(代码中的xy代表将x的逆元求到y里面,后面同理)

inline void getinv(int *x,int *y,int len)
{
    if(len == 1)
    {
        y[0] = ksm(x[0],MOD - 2);
        return;
    }
    getinv(x,y,(len + 1) >> 1);
    getrev((len << 1) - 1);
    for(int i = 0;i < len;i++) c[i] = x[i];
    fill(c + len,c + tt,0);
    NTT(c,1);NTT(y,1);
    for(int i = 0;i < tt;i++) y[i] = (2ll * y[i] % MOD - 1ll * y[i] * y[i] % MOD * c[i] % MOD + MOD) % MOD;
    NTT(y,-1);
    fill(y + len,y + tt,0);
}

多项式除法/多项式取模

给定n次多项式F(x)m次多项式G(x),求多项式Q(x)R(x),使:

  • Q(x)nm次,R(x)小于m次。

  • F(x)=G(x)Q(x)+R(x)

考虑AR(x)为将A系数反转过后取的多项式,有:

AR(x)=A(1x)xn

用这个转化式子:

F(x)=Q(x)G(x)+R(x)

F(1x)=Q(1x)G(1x)+R(1x)

xnF(1x)=xnmQ(1x)xmG(1x)+xnm+1xm1R(1x)

FR(x)=QR(x)GR(x)+xnm+1RR(x)

QR(x)FR(x)1GR(x) (mod xnm+1)

由于QR(x)的次数是nm,所以这样刚好能够求出Q(x),然后

R(x)=F(x)Q(x)G(x)

分别计算即可。

inline void rvs(int *x,int len)
{
	for(int i = 0;len - i - 1 > i;i++) swap(x[i],x[len - i - 1]);
}
inline void div(int *x,int len1,int *y,int len2,int *quo,int *rem)
{
	for(int i = 0;i < len2;i++) GR[len2 - 1 - i] = y[i];
	if(len1 - len2 + 1 <= len2) fill(GR + len1 - len2 + 1,GR + len2,0);
	rvs(x,len1);
	getinv(GR,c,len1 - len2 + 1);
	getrev((len1 + len2) << 1);
	for(int i = 0;i < len1;i++) quo[i] = x[i];
	fill(quo + len1,quo + tt,0);
	NTT(c,1);NTT(quo,1);
	for(int i = 0;i < tt;i++) quo[i] = 1ll * quo[i] * c[i] % MOD;
	NTT(quo,-1);NTT(c,-1);
	fill(quo + len1 - len2 + 1,quo + tt,0);
	rvs(quo,len1 - len2 + 1);
	rvs(x,len1);
	NTT(quo,1);NTT(y,1);
	for(int i = 0;i < tt;i++) y[i] = 1ll * y[i] * quo[i] % MOD;
	NTT(quo,-1);NTT(y,-1);
	fill(quo + len1 - len2 + 1,quo + tt,0);
	for(int i = 0;i < tt;i++) rem[i] = (x[i] - y[i] + MOD) % MOD;
	fill(rem + len2 - 1,rem + tt,0);
}

多项式ln

事实上多项式求ln值基本是不会用到的(哪个闲人会让你求多项式的ln值),但是在例如快速幂这些算法中会用到,所以有一定价值。

给定F(x),求lnF(x) mod xn,保证f0=1

有定理:多项式F(x)有对数多项式当且仅当f0=1(证明请读者自行了解)。

T(x)=lnF(x),我们发现多项式很好求导,而(ln x)=1x,很好计算,考虑求导:

T(x)=F(x)1F(x)

(复合函数求导)

发现这个只需要求逆就好了,容易计算,再将函数做不定积分就可以求出答案。

求导方法:

设一个多项式为i=0n1fixi,则其导函数为i=0n2fi+1(i+1)xi

不定积分反之。

inline void diff(int *x,int len)
{
	for(int i = 0;i < len - 1;i++) x[i] = 1ll * x[i + 1] * (i + 1) % MOD;
	x[len - 1] = 0;
}
inline void intg(int *x,int len)//len是多项式现在的长度
{
	for(int i = len;i >= 1;i--) x[i] = 1ll * x[i - 1] * inv[i] % MOD;
	x[0] = 0;
}
inline void getln(int *x,int *y,int len)
{
	fill(y,y + len * 3,0);
	fill(b,b + len * 3,0);
	for(int i = 0;i < len;i++) y[i] = x[i];
	getinv(y,b,len);
	diff(y,len); 
	getrev(len << 1);
	NTT(y,1);NTT(b,1);
	for(int i = 0;i < tt;i++) y[i] = 1ll * y[i] * b[i] % MOD;
	NTT(y,-1);
	intg(y,len - 1);
	fill(y + len,y + tt,0);
}

多项式exp

首先要从一点说起——牛顿迭代法。

牛顿迭代常用于求一个函数的零点,假定我们有一个曲线:

image

我们任取一个点,做函数的切线,取其与x轴的交点,然后再以这个点的横坐标为x,继续以上过程,就可以快速接近一个零点。

image

假设切点为x0,与x轴交点为x1(暂时不知道),所以切线方程为:

y=f(x0)(xx0)+f(x0)

由于与x轴相交时,y=0,解得

x=x0f(x0)f(x0)

对于多项式来说也一样:

试求G(x),使F(G(x))0,设上次迭代的函数为H(x),则:

G(x)=H(x)F(H(x))F(H(x))

有结论是,这样迭代每次的精度是翻倍的,也就是说假设F(G(x))0 mod xn,则F(H(x))0 mod xn2

考虑将这个代入exp,我们发现:

G(x)eF(x) mod xn

则:

ln G(x)  F(x)0 mod xn

假设T(G(x))=ln G(x)  F(x),则T(G(x))0 mod xn

对这个函数求导:

T(G(x))=1G(x)

因为:

(fg)=fg

(ln x)=1x

c()=0

在这里,由于已知,我们将F(x)看作一个“常数”

代入:

G(x)H(x)ln H(x)  F(x)1H(x) mod xn

G(x)H(x)(1ln H(x)F(x)) mod xn

递归计算即可。

越复杂使用的辅助数组越多,一定要合理分配辅助数组。

inline void getexp(int *x,int *y,int len)
{
	if(len == 1)
	{
		y[0] = 1;
		return;
	}
	getexp(x,y,(len + 1) >> 1);
	fill(e + 0,e + len,0);
	getln(y,e,len);
	for(int i = 0;i < len;i++) e[i] = (x[i] - e[i] + MOD) % MOD;
	e[0]++;
	fill(e + len,e + tt,0);
	getrev(len << 1);
	NTT(e,1);NTT(y,1);
	for(int i = 0;i < tt;i++) y[i] = 1ll * e[i] * y[i] % MOD;
	NTT(y,-1);
	fill(y + len,y + tt,0);
}

多项式快速幂

比较简单:

Fk(x)=ek lnF(x)

lnexp即可。

有结论:次数k可以对模数取模而没有影响。

如果a01,就没有办法求ln了。这时我们直接将整个多项式乘上a0的逆元,快速幂后再乘上原来a0值的k mod ϕ(MOD)倍即可。

如果a0=0,就没有办法求逆元了,这时我们将多项式整体左移至第一位不为0为止,快速幂完再右移即可,注意特判次数乘位移数取模前是否大于n,如果是则全部设为0。

inline void polyksm(int *x,int *y,int pts,int pts2,int len)
{
	int pos = 0,org;
	while(!x[pos] && pos < len) ++pos;
	if(1ll * pts * pos > len) return;
	for(int i = 0;i < len - pos;i++) x[i] = x[i + pos];
	fill(x + len - pos,x + len,0);
	len -= pos;
	org = x[0];
	for(int i = 0;i < len;i++) x[i] = 1ll * x[i] * ksm(org,MOD - 2) % MOD;
	fill(c,c + len * 3,0);
	getln(x,c,len);
	for(int i = 0;i < len;i++) c[i] = 1ll * c[i] * pts % MOD;
	getexp(c,y,len); 
	for(int i = 0;i < len;i++) y[i] = 1ll * y[i] * ksm(org,pts2) % MOD;
	len += pos;
	for(int i = len - 1;i >= pos * pts;i--) y[i] = y[i - pos * pts];
	fill(y,y + pos * pts,0);
}

(没有包含特判部分)。

多项式开根

G(x),使G2(x)F(x)mod xn

倍增。

H2(x)F(x)mod xn2

G2(x)F(x)mod xn2

G2(x)H2(x)0mod xn2

G4(x)2G2(x)H2(x)+H4(x)0mod xn

G4(x)+2G2(x)H2(x)+H4(x)4G2(x)H2(x)modxn

G2(x)+H2(x)2G(x)H(x)mod xn

F(x)+H2(x)2G(x)H(x)modxn

G(x)F(x)+H2(x)2H(x)

递归计算即可,边界值G(0)用二次剩余计算。

inline void sqrt(int *x,int *y,int len)
{
	if(len == 1) 
	{
		y[0] = getsq(x[0]);
		return;
	}
	sqrt(x,y,(len + 1) >> 1);
	fill(c + 0,c + len * 2,0);
	getinv(y,c,len);
	int iv2 = ksm(2,MOD - 2);
	getrev(len << 1);
	for(int i = 0;i < len;i++) help[i] = x[i];
	fill(help + len,help + tt,0);
	NTT(help,1);NTT(c,1);
	for(int i = 0;i < tt;i++) help[i] = 1ll * help[i] * c[i] % MOD;
	NTT(help,-1);
	for(int i = 0;i < len;i++) y[i] = 1ll * (y[i] + help[i]) % MOD * iv2 % MOD;
	fill(y + len,y + tt,0);
}

任意模数:三模数NTT

对于任意模数取模,如果n105,ai109,我们发现最终系数小于1023。所以我们取3个便于计算的模数,并且相乘大于1023,最后CRT合并,就可以得到正确答案。

证明:咕。

#include<bits/stdc++.h>
using namespace std;
const int N = 4e5 + 5,MOD[3] = {998244353,1004535809,469762049},g = 3,gi[3] = {332748118,334845270,156587350};
inline int ksm(int base,int pts,int i)
{
	int ret = 1;
	for(;pts > 0;pts >>= 1,base = 1ll * base * base % MOD[i])
		if(pts & 1)
			ret = 1ll * ret * base % MOD[i];
	return ret;
}
inline void exgcd(__int128 a,__int128 b,__int128 &x,__int128 &y)
{
	if(b == 0)
	{
		x = 1,y = 0;
		return;
	}
	exgcd(b,a % b,x,y);
	__int128 t = y;
	y = x - (a / b) * y;
	x = t;
}
int rev[N],tt,tw,F[N],G[N],R[N],a[N],b[N],ans[4][N],n,m,p;
inline void getrev(int len)
{
	tt = 1,tw = 0;
	while(tt < len) tt <<= 1,tw++;
	for(int i = 1;i < tt;i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (tw - 1));
 } 
inline void NTT(int *x,int type,int mod)
{
	for(int i = 0;i < tt;i++) if(i > rev[i]) swap(x[i],x[rev[i]]);
	for(int mid = 1;mid < tt;mid <<= 1)
	{
		int omega = ksm((type == 1) ? g : gi[mod],(MOD[mod] - 1) / (mid << 1),mod);
		for(int i = 0,R = mid << 1;i < tt;i += R)
		{
			int w = 1;
			for(int j = i;j < i + mid;j++,w = 1ll * w * omega % MOD[mod])
			{
				int X = x[j],Y = 1ll * x[j + mid] * w % MOD[mod];
				x[j] = (X + Y) % MOD[mod];
				x[j + mid] = (X - Y + MOD[mod]) % MOD[mod];
			}
		}
	}
	if(type == -1)
	{
		int iv = ksm(tt,MOD[mod] - 2,mod);
		for(int i = 0;i < tt;i++) x[i] = 1ll * x[i] * iv % MOD[mod];
	}
}
inline int crt(int x,int y,int z)
{
	__int128 M = MOD[0] * 1ll * MOD[1];
	M *= MOD[2];
	__int128 iv10,iv20,iv01,iv21,iv02,iv12;
	iv10 = ksm(MOD[1],MOD[0] - 2,0);
	iv20 = ksm(MOD[2],MOD[0] - 2,0);
	iv01 = ksm(MOD[0],MOD[1] - 2,1);
	iv21 = ksm(MOD[2],MOD[1] - 2,1);
	iv02 = ksm(MOD[0],MOD[2] - 2,2);
	iv12 = ksm(MOD[1],MOD[2] - 2,2);
	__int128 ret = 0;
	ret = (1ll * x * MOD[1] % M * MOD[2] % M * iv10 % M * iv20 % M + 1ll * y * MOD[0] % M * MOD[2] % M * iv01 % M * iv21 % M) % M;
	ret = (ret + 1ll * z * MOD[0] % M * MOD[1] % M * iv02 % M * iv12 % M) % M;
	return ret % p;
}
int main()
{
	cin>>n>>m>>p;++n;++m;
	for(int i = 0;i < n;i++) cin>>F[i];
	for(int i = 0;i < m;i++) cin>>G[i];
	getrev(m + n);
	for(int i = 0;i < 3;i++)
	{
		memset(a,0,sizeof(a));memset(b,0,sizeof(b));memset(ans[i],0,sizeof(ans[i])); 
		for(int j = 0;j < n;j++) a[j] = F[j];
		for(int j = 0;j < m;j++) b[j] = G[j];
		NTT(a,1,i);NTT(b,1,i);
		for(int j = 0;j < tt;j++) ans[i][j] = 1ll * a[j] * b[j] % MOD[i];
		NTT(ans[i],-1,i);
	}
	for(int i = 0;i < n + m;i++) ans[3][i] = crt(ans[0][i],ans[1][i],ans[2][i]);
	for(int i = 0;i < n + m - 1;i++) cout<<ans[3][i]<<" ";
	return 0;
 } 

分治NTT

给定n次多项式G(x)G(0)=0,求F(x)

其中fi=j=1ifijgj。边界为f0=1

这个F要依赖前项进行运算,不能直接卷积,我们考虑类cdq分治的方法。

假设我们已经计算了fl,mid,考虑fl,midfmid+1,r的贡献。考虑取g的多少项,fl+grlfr。所以我们复制g0grl,将fl,midg0,rl做加法卷积贡献即可。

inline void cdqNTT(int *x,int *y,int l,int r)//[l,r)
{
	if(l == r - 1)
	{
		if(!l) x[l] = 1;
		return;
	}
	int mid = (l + r) >> 1;
	cdqNTT(x,y,l,mid);
	getrev(r - l);
	for(int i = 0;i < mid - l;i++) a[i] = x[i + l];
	fill(a + mid - l,a + tt,0);
	for(int i = 0;i < r - l;i++) b[i] = y[i];
	fill(b + r - l,b + tt,0);
	NTT(a,1);NTT(b,1);
	for(int i = 0;i < tt;i++) a[i] = 1ll * a[i] * b[i] % MOD;
	NTT(a,-1);
	for(int i = mid;i < r;i++) x[i] = (x[i] + a[i - l]) % MOD;
	cdqNTT(x,y,mid,r);
}

完结撒花

posted @   The_Last_Candy  阅读(39)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示