多项式全家桶(还有些不会的)
多项式全家桶
记号约定
- \(f^{(i)}(x)\):对 \(f(x)\) 求 \(i\) 阶导。
- \([x^i]f(x)\):\(f(x)\) 的 \(i\) 次项系数。
多项式微积分
我们知道:
以及可加性,于是容易写出代码:
#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;
}
时间复杂度 \(\mathcal O(n)\)。
多项式加减法
对应项相加减即可,于是我都没封装。
时间复杂度 \(\mathcal 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(\frac{n}{2})+\mathcal O(n)=\mathcal O(n\log n)\)。
任意模数多项式乘法
还不会。
多项式牛顿迭代
前置知识:牛顿迭代、泰勒展开。请先自行学习。
多项式牛顿迭代并不会被封装,但它是一个十分重要的推导方法,下面都会用到。
多项式牛顿迭代用于解决这样的问题:给定多项式 \(g(x)\),求多项式 \(f(x)\) 使得 \(g(f(x))\equiv 0\pmod{x^n}\)。
首先,\([x^0]g(f(x))\equiv 0\) 也就是 \(g(f(x))\equiv 0\pmod{x}\) 的解需要单独求出。
然后设 \(f_0\) 是 \(g(f(x))\equiv 0\pmod{x^{\left\lceil\frac{n}{2}\right\rceil}}\) 的解。
对 \(g(f(x))\) 在 \(f_0(x)\) 处进行泰勒展开:
因为 \(f(x)-f_0(x)\) 的最低非零项次数最低为 \(\left\lceil\frac{n}{2}\right\rceil\),所以 \(\forall i\ge 2:(f(x)-f_0(x))^i\equiv 0\pmod{x^n}\),进一步得到:
多项式乘法逆元
设我们要求给定函数 \(h(x)\) 的乘法逆元,则有方程:
应用多项式牛顿迭代可得:
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(\frac{n}{2})+\mathcal O(n\log n)=\mathcal O(n\log n)\)。
多项式开根
设我们要求 \(f(x)\) 满足 \(f^2(x)\equiv h(x)\pmod{x^n}\)。
首先在 \(n=1\) 时需要单独求出。如果题目保证了 \([x^0]h(x)=1\),则可以直接 \([x^0]f(x)=1\),否则需要使用 二次剩余。
有方程:
应用多项式牛顿迭代可得:
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(\frac{n}{2})+\mathcal O(n\log n)=\mathcal O(n\log n)\)。
多项式对数函数
对于多项式 \(h(x)\),若 \(\ln h(x)\) 存在,则 \([x^0]h(x)=1\)。
遇事不决先求导:
然后两边积分:
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);
}
时间复杂度为 \(\mathcal O(n\log n)\)。
多项式指数函数
对于多项式 \(h(x)\),若 \(\exp h(x)\) 存在,则 \([x^0]h(x)=0\),否则 \(\exp h(x)\) 的常数项不收敛。
有方程:
应用多项式牛顿迭代可得:
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(\frac{n}{2})+\mathcal O(n\log n)=\mathcal O(n\log n)\)。
多项式带余除法
已知 \(f(x),g(x)\),求 \(f(x)\) 除以 \(g(x)\) 得到的商 \(Q(x)\) 和余数 \(R(x)\)。
如果可以消除余数 \(R(x)\) 的影响,则多项式求逆直接就解决了。
构造变换:
也就是翻转 \(f(x)\) 的各项系数。
我们记 \(n=\deg f,m=\deg g\)。
将 \(f(x)=g(x)Q(x)+R(x)\) 中的 \(x\) 替换为 \(\frac{1}{x}\),并把两边乘以 \(x^n\),得到:
则:
使用多项式求逆可以得到 \(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;
}
}
}
时间复杂度为 \(\mathcal O(n\log n)\)。
多项式幂函数
普通的多项式快速幂计算 \(f^k(x)\) 的复杂度为 \(\mathcal O(n\log n\log k)\)。
当 \([x^0]f(x)=1\) 时,有:
当 \([x^0]f(x)\ne 1\) 时,我们想到除以 \([x^0]f(x)\) 把 \([x^0]f(x)\) 变成 \(1\)。不过当 \([x^0]f(x)=0\) 时会出问题,我们需要找到最低非零项 \(f_tx^t\),则:
先放一个前者的代码,后者还没写:
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);
}
时间复杂度为 \(\mathcal O(n\log n)\)。
多项式三角函数
由欧拉公式 \(\textrm e^{\textrm ix}=\cos x+\textrm i\sin x\),我们知道:
代入 \(f(x)\) 得:
然后 \(\tan f(x)=\frac{\sin f(x)}{\cos f(x)}\) 可以得到正切函数。
那这里的 \(\textrm i\) 是什么?由定义 \(\textrm i\equiv\sqrt{-1}\equiv\sqrt{998244352}\pmod{998244353}\),令 \(\textrm i\) 为 \(998244352\) 模意义开根的结果即可,得到 \(\textrm i=86583718\) 或者 \(\textrm 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;
}
}
时间复杂度为 \(\mathcal O(n\log n)\)。
多项式反三角函数
遇事不决先求导:
代入 \(f\left(x\right)\) 得:
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);
}
}
时间复杂度为 \(\mathcal O(n\log n)\)。
多项式多点求值
还不会。
多项式快速插值
还不会。