多项式全家桶(还有些不会的)

多项式全家桶

记号约定

  • f(i)(x):对 f(x)i 阶导。
  • [xi]f(x)f(x)i 次项系数。

多项式微积分

我们知道:

ddxxn=nxn1xndx=xn+1n+1

以及可加性,于是容易写出代码:

#define rep(x,y,z) for(int x=(y);x<=(z);x++)
#define per(x,y,z) for(int x=(y);x>=(z);x--)
void PolyDer(ll* a, ll* b, ll n) {
	rep(i, 1, n-1) b[i-1] = a[i] * i % mod;
	b[n-1] = 0;
}
void PolyInt(ll* a, ll* b, ll n) {
	rep(i, 1, n-1) b[i] = a[i-1] * inv(i) % mod;
	b[0] = 0;
}

时间复杂度 O(n)

多项式加减法

对应项相加减即可,于是我都没封装。

时间复杂度 O(n)

多项式乘法

前置知识:FFT/NTT。其中 FFT 用于无模数情况,NTT 用于有 NTT 模数情况。

ll k = 1;
for(;k<=n+m;k<<=1);
NTT(a, k, 1); NTT(b, k, 1);
rep(i, 0, k-1) a[i] = a[i] * b[i] % mod;
NTT(a, k, -1);

时间复杂度为 NTT 的 T(n)=2T(n2)+O(n)=O(nlogn)

任意模数多项式乘法

还不会。

多项式牛顿迭代

前置知识:牛顿迭代、泰勒展开。请先自行学习。

多项式牛顿迭代并不会被封装,但它是一个十分重要的推导方法,下面都会用到。

多项式牛顿迭代用于解决这样的问题:给定多项式 g(x),求多项式 f(x) 使得 g(f(x))0(modxn)

首先,[x0]g(f(x))0 也就是 g(f(x))0(modx) 的解需要单独求出。

然后设 f0g(f(x))0(modxn2) 的解。

g(f(x))f0(x) 处进行泰勒展开:

i=0+g(i)(f0(x))i!(f(x)f0(x))i0(modxn)

因为 f(x)f0(x) 的最低非零项次数最低为 n2,所以 i2:(f(x)f0(x))i0(modxn),进一步得到:

g(f0(x))+g(f0(x))(f(x)f0(x))0(modxn)f(x)f0(x)g(f0(x))g(f0(x))(modxn)

多项式乘法逆元

设我们要求给定函数 h(x) 的乘法逆元,则有方程:

g(f(x))=1f(x)h(x)0(modxn)

应用多项式牛顿迭代可得:

f(x)f0(x)1f0(x)h(x)1f02(x)(modxn)2f0(x)f02(x)h(x)(modxn)

void PolyInv(ll* a, ll* b, ll n) {
	if(n == 1) {b[0] = inv(a[0]); return;}
	PolyInv(a, b, (n+1)>>1);
	ll k = 1;
	for(;k<=(n<<1);k<<=1);
	rep(i, 0, k-1) tmp[i] = i < n ? a[i] : 0;
	NTT(b, k, 1); NTT(tmp, k, 1);
	rep(i, 0, k-1) b[i] = b[i] * (2 - b[i] * tmp[i] % mod + mod) % mod;
	NTT(b, k, -1);
	rep(i, 0, k-1) b[i] = i < n ? b[i] : 0;
}

时间复杂度为 T(n)=T(n2)+O(nlogn)=O(nlogn)

多项式开根

设我们要求 f(x) 满足 f2(x)h(x)(modxn)

首先在 n=1 时需要单独求出。如果题目保证了 [x0]h(x)=1,则可以直接 [x0]f(x)=1,否则需要使用 二次剩余

有方程:

g(f(x))=f2(x)h(x)0(modxn)

应用多项式牛顿迭代可得:

f(x)f0(x)f02(x)h(x)2f0(x)(modxn)f02(x)+h(x)2f0(x)(modxn)

void PolySqrt(ll* a, ll* b, ll n) {
	// if(n == 1) {b[0] = 1; return;}
	if(n == 1) {mt19937 rnd(time(0)); b[0] = cipolla(rnd, a[0])[0]; return;}
	PolySqrt(a, b, (n+1)>>1);
	ll k = 1;
	for(;k<=(n<<1);k<<=1);
	rep(i, 0, k-1) finv[i] = 0;
	PolyInv(b, finv, n);
	rep(i, 0, k-1) tmp[i] = i < n ? a[i] : 0;
	NTT(b, k, 1); NTT(tmp, k, 1); NTT(finv, k, 1);
	rep(i, 0, k-1) b[i] = (tmp[i] * finv[i] % mod + b[i]) % mod * inv2 % mod;
	NTT(b, k, -1);
	rep(i, 0, k-1) b[i] = i < n ? b[i] : 0;
}

时间复杂度为 T(n)=T(n2)+O(nlogn)=O(nlogn)

多项式对数函数

对于多项式 h(x),若 lnh(x) 存在,则 [x0]h(x)=1

遇事不决先求导:

dlnh(x)dxh(x)h(x)(modxn)

然后两边积分:

lnh(x)h(x)h(x)dx(modxn)

void PolyLn(ll* a, ll* b, ll n) {
	ll k = 1;
	for(;k<=(n<<1);k<<=1);
	rep(i, 0, k-1) finv[i] = 0;
	PolyInv(a, finv, n);
	rep(i, 0, k-1) tmp[i] = 0;
	PolyDer(a, tmp, n);
	NTT(tmp, k, 1); NTT(finv, k, 1);
	rep(i, 0, k-1) tmp[i] = tmp[i] * finv[i] % mod;
	NTT(tmp, k, -1);
	PolyInt(tmp, b, n);
}

时间复杂度为 O(nlogn)

多项式指数函数

对于多项式 h(x),若 exph(x) 存在,则 [x0]h(x)=0,否则 exph(x) 的常数项不收敛。

有方程:

g(f(x))=lnf(x)h(x)0(modxn)

应用多项式牛顿迭代可得:

f(x)f0(x)lnf0(x)h(x)1f0(x)(modxn)f0(x)(1lnf0(x)+h(x))(modxn)

void PolyExp(ll* a, ll* b, ll n) {
	if(n == 1) {b[0] = 1; return;}
	PolyExp(a, b, (n+1)>>1);
	ll k = 1;
	for(;k<=(n<<1);k<<=1);
	rep(i, 0, k-1) gln[i] = 0;
	PolyLn(b, gln, n);
	rep(i, 0, k-1) tmp[i] = 0;
	rep(i, 0, n-1) tmp[i] = ((!i) - gln[i] + a[i] + mod) % mod;
	NTT(tmp, k, 1); NTT(b, k, 1);
	rep(i, 0, k-1) b[i] = b[i] * tmp[i] % mod;
	NTT(b, k, -1);
}

时间复杂度为 T(n)=T(n2)+O(nlogn)=O(nlogn)

多项式带余除法

已知 f(x),g(x),求 f(x) 除以 g(x) 得到的商 Q(x) 和余数 R(x)

如果可以消除余数 R(x) 的影响,则多项式求逆直接就解决了。

构造变换:

fR(x)=xdegff(1x)

也就是翻转 f(x) 的各项系数。

我们记 n=degf,m=degg

f(x)=g(x)Q(x)+R(x) 中的 x 替换为 1x,并把两边乘以 xn,得到:

xnf(1x)=xmg(1x)xnmQ(1x)+xnm+1xm1R(1x)fR(x)=gR(x)QR(x)+xnm+1RR(x)

则:

fR(x)gR(x)QR(x)(modxnm+1)

使用多项式求逆可以得到 Q(x),代回原式可得 R(x)

void PolyDiv(ll* a, ll* b, ll* q, ll* r, ll n, ll m) {
	rep(i, 0, n-1) gr[i] = i < m ? b[m-1-i] : 0;
	PolyInv(gr, finv, n-m+1);
	ll k = 1;
	for(;k<=((n-m+1)<<1);k<<=1);
	rep(i, 0, n-1) fr[i] = i < n - m + 1 ? a[n-1-i] : 0;
	NTT(finv, k, 1); NTT(fr, k, 1);
	rep(i, 0, k-1) tmp[i] = finv[i] * fr[i] % mod;
	NTT(tmp, k, -1);
	if(q) {
		rep(i, 0, n-m) q[i] = tmp[n-m-i];
		if(r) {
			reverse(tmp, tmp+n-m+1);
			reverse(gr, gr+m);
			k = 1;
			for(;k<=n;k<<=1);
			rep(i, 0, k-1) tmp[i] = i <= n - m ? tmp[i] : 0;
			rep(i, 0, k-1) gr[i] = i < m ? gr[i] : 0;
			NTT(tmp, k, 1); NTT(gr, k, 1);
			rep(i, 0, k-1) tmp[i] = tmp[i] * gr[i] % mod;
			NTT(tmp, k, -1);
			rep(i, 0, m-2) r[i] = (a[i] - tmp[i] + mod) % mod;
		}
	}
}

时间复杂度为 O(nlogn)

多项式幂函数

普通的多项式快速幂计算 fk(x) 的复杂度为 O(nlognlogk)

[x0]f(x)=1 时,有:

fk(x)=exp(klnf(x))

[x0]f(x)1 时,我们想到除以 [x0]f(x)[x0]f(x) 变成 1。不过当 [x0]f(x)=0 时会出问题,我们需要找到最低非零项 ftxt,则:

fk(x)=ftkxtkexp(klnf(x)ftxt)

先放一个前者的代码,后者还没写:

void PolyPow(ll* a, ll* b, ll n, ll k) {
	PolyLn(a, fln, n);
	rep(i, 0, n-1) fln[i] = fln[i] * k % mod;
	PolyExp(fln, b, n);
}

时间复杂度为 O(nlogn)

多项式三角函数

由欧拉公式 eix=cosx+isinx,我们知道:

sinx=eixeix2icosx=eix+eix2

代入 f(x) 得:

sinf(x)=exp(if(x))exp(if(x))2icosf(x)=exp(if(x))+exp(if(x))2

然后 tanf(x)=sinf(x)cosf(x) 可以得到正切函数。

那这里的 i 是什么?由定义 i1998244352(mod998244353),令 i998244352 模意义开根的结果即可,得到 i=86583718 或者 i=911660635

void PolyTri(ll* a, ll* sin, ll* cos, ll* tan, ll n) {
	ll imunit = qpow(g, (mod-1)>>2);
	rep(i, 0, n-1) tmp2[i] = a[i] * imunit % mod;
	rep(i, 0, n-1) foppo[i] = (mod - a[i]) % mod * imunit % mod;
	PolyExp(tmp2, fexp, n); PolyExp(foppo, gexp, n);
	if(sin) rep(i, 0, n-1) sin[i] = (fexp[i] - gexp[i] + mod) % mod * inv(2*imunit) % mod;
	if(cos) rep(i, 0, n-1) cos[i] = (fexp[i] + gexp[i]) % mod * inv2 % mod;
	if(sin && cos && tan) {
		PolyInv(cos, finv, n);
		ll k = 1;
		for(;k<=(n<<1);k<<=1);
		NTT(sin, k, 1); NTT(finv, k, 1);
		rep(i, 0, k-1) tan[i] = sin[i] * finv[i] % mod;
		NTT(sin, k, -1); NTT(tan, k, -1);
		rep(i, 0, k-1) tan[i] = i < n ? tan[i] : 0;
	}
}

时间复杂度为 O(nlogn)

多项式反三角函数

遇事不决先求导:

ddxarcsinx=11x2arcsinx=11x2dxddxarccosx=11x2arccosx=11x2dxddxarctanx=11+x2arctanx=11+x2dx

代入 f(x) 得:

ddxarcsinf(x)=f(x)1f2(x)arcsinf(x)=f(x)1f2(x)dxddxarccosf(x)=f(x)1f2(x)arccosf(x)=f(x)1f2(x)dxddxarctanf(x)=f(x)1+f2(x)arctanf(x)=f(x)1+f2(x)dx

void PolyATri(ll* a, ll* asin, ll* acos, ll* atan, ll n) {
	ll k = 1;
	for(;k<=(n<<1);k<<=1);
	rep(i, 0, k-1) f2[i] = i < n ? a[i] : 0;
	NTT(f2, k, 1);
	rep(i, 0, k-1) f2[i] = f2[i] * f2[i] % mod;
	NTT(f2, k, -1);
	if(asin) {
		rep(i, 0, k-1) tmp2[i] = ((!i) - f2[i] + mod) % mod, fsqrt[i] = 0;
		PolySqrt(tmp2, fsqrt, n);
		rep(i, 0, k-1) tmp3[i] = 0;
		PolyInv(fsqrt, tmp3, n);
		rep(i, 0, k-1) tmp[i] = 0;
		PolyDer(a, tmp, n);
		NTT(tmp, k, 1); NTT(tmp3, k, 1);
		rep(i, 0, k-1) tmp[i] = tmp[i] * tmp3[i] % mod;
		NTT(tmp, k, -1);
		PolyInt(tmp, asin, n);
	}
	if(acos) {
		PolyATri(a, acos, nullptr, nullptr, n);
		rep(i, 0, n-1) acos[i] = (mod - acos[i]) % mod;
	}
	if(atan) {
		rep(i, 0, k-1) tmp2[i] = ((!i) + f2[i]) % mod, tmp3[i] = 0;
		PolyInv(tmp2, tmp3, n);
		rep(i, 0, k-1) tmp[i] = 0;
		PolyDer(a, tmp, n);
		NTT(tmp, k, 1); NTT(tmp3, k, 1);
		rep(i, 0, k-1) tmp[i] = tmp[i] * tmp3[i] % mod;
		NTT(tmp, k, -1);
		PolyInt(tmp, atan, n);
	}
}

时间复杂度为 O(nlogn)

多项式多点求值

还不会。

多项式快速插值

还不会。

posted @   rui_er  阅读(111)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
历史上的今天:
2021-07-12 从零开始重学动态规划(最后更新:2021.10.3)
2021-07-12 【洛谷 P3396 哈希冲突】解题报告(根号分治)
点击右上角即可分享
微信分享提示