多项式插值
Keep away from polynomial. ---- Wild_Donkey
Published on 2022-02-20, Updated on 2022-07-02
给 (x0,y0),(x1,y1),...,(xn,yn), 共 n+1 个点. 求一个 n 次 n+1 项的多项式 L, 使得多项式的图像过每一个点. 这个多项式 L 便是拉格朗日多项式 (Lagrange polynomial).
拉格朗日基本多项式 (插值基函数)
ℓi 是一个多项式, 满足 ℓ(xi)=1, 且 ℓ(xj)=0 (j=i).
ℓi(x)=j=0,j=i∏nxi−xjx−xj
这样当 x=xi 时, 所有 xi−xjx−xj 的分子等于分母. 当 x=xi 时, 一定存在一个项分子为 0, 所以乘起来还是 0.
拉格朗日多项式
因为每个拉格朗日基本多项式 ℓi 在除了 xi 点以外的给定的横坐标上都是 0, 所以我们把所有拉格朗日多项式加权求和就能得到符合要求的多项式 L.
L(x)=i=0∑nyiℓi(x)L(x)=i=0∑nyij=0,j=i∏nxi−xjx−xj
重心拉格朗日插值法
为了方便计算, 预处理一个 n+1 次多项式 ℓ:
ℓ(x)=i=0∏n(x−xi)
这个多项式可以进行 O(n) 次耗时 O(n) 的 O(n) 项式和二项式乘法得到, 总耗时 O(n2).
定义重心权 wi:
wi=j=0,j=i∏n(xi−xj)1
这样就可以得到:
ℓi(x)L(x)L(x)=x−xiℓ(x)wi=i=0∑nx−xiℓ(x)wiyi=ℓ(x)i=0∑nx−xiwiyi
我们可以 O(n) 地算出 wiyi, 然后 O(n) 地算出 x−xiℓ(x), 在除法过程中就可以直接统计 ℓi 堆 L 的贡献. 所以在预处理之后, 计算 L(x) 需要 O(n2) 的时间.
代码实现
模板题
代码还是很好理解的, 只需要把前面的式子用程序实现就可以了.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| const unsigned long long Mod(998244353); unsigned long long Pnt[2005][2], La[2005], L[2005], Ans(0), m, Now(1); unsigned n; unsigned A, B, C, D, t; unsigned Cnt(0); inline unsigned long long Inv(unsigned long long x) { unsigned long long Rt(1); unsigned y(998244351); while (y) { if (y & 1) Rt = Rt * x % Mod; x = x * x % Mod, y >>= 1; } return Rt; } signed main() { n = RD(), m = RD(); for (unsigned i(0); i < n; ++i) Pnt[i][0] = RD(), Pnt[i][1] = RD(); La[1] = 1, La[0] = Mod - Pnt[0][0]; for (unsigned i(1); i < n; ++i) { for (unsigned j(i + 1); j; --j) La[j] = (La[j] * (Mod - Pnt[i][0]) + La[j - 1]) % Mod; La[0] = La[0] * (Mod - Pnt[i][0]) % Mod; } for (unsigned i(0); i < n; ++i) { unsigned long long Mul(1), Tmp(La[n]); for (unsigned j(0); j < n; ++j) if (j ^ i) Mul = Mul * (Mod + Pnt[i][0] - Pnt[j][0]) % Mod; Mul = Inv(Mul) * Pnt[i][1] % Mod; for (unsigned j(n - 1); ~j; --j) { L[j] = (L[j] + Mul * Tmp) % Mod; Tmp = (La[j] + Tmp * Pnt[i][0]) % Mod; } } for (unsigned i(0); i < n; ++i) { Ans = (Ans + L[i] * Now) % Mod; Now = Now * m % Mod; } printf("%llu\n", Ans); return Wild_Donkey; }
|
加点
如果我们需要对已经插值的 n 个点加入一个点, 如果重新插值, 需要 O(n2) 的时间.
考虑插入一个点 (xn+1,yn+1) 对 L(x) 的影响, 它会使插入后 ℓ′(x)=ℓ(x)(x−xn+1). 会使每个满足 i≤n 的 wi 都乘以 xi−xn+11. 还会使 L(x) 加上 x−xn+1ℓ′(x)wn+1yn+1.
L′(x)=ℓ′(x)(i=0∑n+1x−xiwi′yi)
我们用 O(n) 处理出 wi′ (i≤n), 然后 O(n) 直接算出 wn+1′. 我们可以在 O(n) 的时间内做乘法得到 ℓ′(x). 维护了 w 数组和多项式 ℓ(x) 就实现了对 L(x) 的维护. 需要时 O(n2) 可以求出多项式 L(x).
如果问题只需要维护多项式 L(x) 的一个点值 y, 那么问题就更简单了, 我们可以直接维护 ℓ(x) 的点值, 这样就可以把多项式除法变成整数乘除, 实现直接维护 y 并实现 O(n) 插入.
第二型重心拉格朗日插值法
现在有另一组点, 每个点和我们要插值的点一一对应, 每一对点的横坐标相同, 而新给出的这些点纵坐标是 1. 我们对新点进行插值, 得到多项式 g(x), 无论 x 取什么值, g(x)=1. 如果我们代入拉格朗日插值公式, 可以得到:
g(x)=ℓ(x)i=0∑nx−xiwi
因为任何数除以 1 还是它本身, 所以 L(x)=g(x)L(x). 将 L(x), g(x) 都用拉格朗日插值公式表示, 则得到:
L(x)=g(x)L(x)=ℓ(x)i=0∑nx−xiwiℓ(x)i=0∑nx−xiwiyi=i=0∑nx−xiwii=0∑nx−xiwiyi
我们可以只维护 w 数组, 实现 O(n) 求值, O(n) 插入.
差商/均差 (Divided Diffrences)
对于点 (xi,yi),(xi+1,yi+1),...,(xi+j,yi+j), 用 [y]
对于函数 f(x), 用 f[xi,xi+1,...,xi+j] 表示函数的点 (xi,f(xi)),(xi+1,f(xi+1)),...,(xi+j,f(xi+j)) 的均差.
均差的预算规则是这样的:
单点的均差就是这个点的纵坐标: f[xi]=f(xi)
多点均差有递归定义: f[xi,xi+1,...,xi+j]=xi+j−xif[xi+1,...,xi+j]−f[xi,...,xi+j−1]
均差有前向和后向之分, 一般只会用到前向均差, 后向均差是把元素顺序倒过来写, 计算上也有一部分需要翻转.
我们可以递推地在 O(n2) 的时间内求出 n 个点的序列的每个子串的均差.
均差的展开形式是这样的:
f[xl,xl+1,...,xr]=i=l∑rj=l,j=i∏rxi−xjf(xi)
用归纳法证明, 单点的均差符合 f[xl]=1f(xl)=f(xl). 假设大小小于 r−l+1 的点集都满足这个式子, 验证大小为 r−l+1 的点集是否满足.
f[xl,xl+1,...,xr]f[xl,xl+1,...,xr]f[xl,xl+1,...,xr]f[xl,xl+1,...,xr]f[xl,xl+1,...,xr]f[xl,xl+1,...,xr]f[xl,xl+1,...,xr]f[xl,xl+1,...,xr]f[xl,xl+1,...,xr]f[xl,xl+1,...,xr]f[xl,xl+1,...,xr]=xi+j−xif[xi+1,...,xi+j]−f[xi,...,xi+j−1]=xr−xli=l+1∑rj=l+1,j=i∏rxi−xjf(xi)−i=l∑r−1j=l,j=i∏r−1xi−xjf(xi)=xr−xli=l+1∑r−1j=l+1,j=i∏rxi−xjf(xi)−i=l+1∑r−1j=l,j=i∏r−1xi−xjf(xi)+j=l+1∏r−1xr−xjf(xr)−j=l+1∏r−1xl−xjf(xl)=xr−xli=l+1∑r−1j=l+1,j=i∏rxi−xjf(xi)−i=l+1∑r−1j=l,j=i∏r−1xi−xjf(xi)+j=l∏r−1xr−xjf(xr)+j=l+1∏rxl−xjf(xl)=xr−xli=l+1∑r−1j=l+1,j=i∏rxi−xjf(xi)−j=l,j=i∏r−1xi−xjf(xi)+j=l∏r−1xr−xjf(xr)+j=l+1∏rxl−xjf(xl)=xr−xli=l+1∑r−1j=l+1,j=i∏r−1xi−xjxi−xrf(xi)−xi−xlf(xi)+j=l∏r−1xr−xjf(xr)+j=l+1∏rxl−xjf(xl)=xr−xli=l+1∑r−1j=l+1,j=i∏r−1xi−xj(xi−xr)(xi−xl)f(xi)((xi−xl)−(xi−xr))+j=l∏r−1xr−xjf(xr)+j=l+1∏rxl−xjf(xl)=xr−xli=l+1∑r−1j=l+1,j=i∏r−1xi−xj(xi−xr)(xi−xl)f(xi)(xr−xl)+j=l∏r−1xr−xjf(xr)+j=l+1∏rxl−xjf(xl)=i=l+1∑r−1j=l+1,j=i∏r−1xi−xj(xi−xr)(xi−xl)f(xi)+j=l∏r−1xr−xjf(xr)+j=l+1∏rxl−xjf(xl)=i=l+1∑r−1j=l,j=i∏rxi−xjf(xi)+j=l∏r−1xr−xjf(xr)+j=l+1∏rxl−xjf(xl)=i=l∑rj=l,j=i∏rxi−xjf(xi)
展开式得证.
牛顿基本多项式
设多项式 ni(x) 满足在 x0,x1,...,xj−1 处都取 0 的多项式. 显然有:
ni(x)=j=0∏i−1(x−xj)
牛顿多项式
我们只要给每个牛顿基本多项式加权求和, 就能构造一个 n 次多项式, 过全部 n+1 个点. 这个总和便是牛顿多项式 N(x).
假设给 ni(x) 加的权为 ai, 写出定义式:
N(x)=i=0∑naini(x)=i=0∑naij=0∏i−1(x−xj)
因为 n+1 个横坐标不同的点可以唯一地确定一个 n 次多项式, 因此相同点集满足 L(x)=N(x). 我们可以用拉格朗日插值公式来求 a. 已经求出已经插了 (x0,y0),...,(xn−1,yn−1) 的拉格朗日多项式 L(x). 我们尝试给 an 赋值, 使得 L(xn)+annn(xn) 为 yn.
ynynani=0∏n−1(xn−xi)ananananan=L(xn)+annn(xn)=i=0∑n−1yij=0,j=i∏n−1xi−xjxn−xj+ani=0∏n−1(xn−xi)=yn−i=0∑n−1yij=0,j=i∏n−1xi−xjxn−xj=i=0∏n−1(xn−xi)yn−i=0∑n−1xn−xiyij=0,j=i∏n−1xi−xj1=i=0∏n−1(xn−xi)yn+i=0∑n−1xi−xnyij=0,j=i∏n−1xi−xj1=i=0∏n−1(xn−xi)yn+i=0∑n−1yij=0,j=i∏nxi−xj1=i=0∏n−1(xn−xi)yn+i=0∑n−1j=0,j=i∏nxi−xjyi=i=0∑nj=0,j=i∏nxi−xjyi
我们发现 an=i=0∑nj=0,j=i∏nxi−xjyi=[y0,y1,...,yn].
所以牛顿多项式便是:
N(x)=i=0∑naini(x)=i=0∑naij=0∏i−1(x−xj)=i=0∑n[y0,y1,...,yi]j=0∏i−1(x−xj)=i=0∑n(j=0∑ik=0,k=j∏ixj−xkyj)j=0∏i−1(x−xj)
横坐标为连续正整数时的线性插值
尝试化简横坐标为连续正整数的拉格朗日多项式:
j=0,j=i∏n(i−j)=(j=0∏i−1(i−j))(j=i+1∏n(i−j))=(j=0∏i−1(i−j))(j=i+1∏n(j−i))(−1)n−i=i!(n−i)!(−1)n−i
记 xi=∏j=0i−1(x−j), 这种情况的拉格朗日多项式可以改写为:
L(x)=i=0∑n(x−n−1)!i!(n−i)!(−1)n−i(x−i)x!f(i)=xn+1i=0∑ni!(n−i)!(−1)n−i(x−i)f(i)
可以发现这个时候如果只需要求一个点值, 可以直接线性求出, 求多个点值也会有很优秀的性质, 接下来用两道题来分别实现一下单点插值和多点插值的情况.
求前 n 个正整数的 k 次方和对 109+7 取模的结果. (n≤109,k≤106)
在 n≤k+1 的时候, 我们可以直接快速幂在 O(nlogk) 的时间内得到答案. 我们知道自然数的 k 次方和公式是一个 k+1 次的多项式, 所以在 n>k+1 的时候, 我们只要想办法插出这个多项式直接求 x=n 的值即可. 我们可以自己选择点值的横坐标, 前面已经推出来, 拉格朗日多项式在点的横坐标为连续的自然数的时候是这样的形式:
L(x)L(x)=xn+1i=0∑ni!(n−i)!(−1)n−i(x−i)ik=i=0∑ni!(n−i)!(−1)n−iikxi(x−n)n−i
因此 O(klogk) 求出一些点值, 线性求出 L(k) 即可.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| const unsigned long long Mod(1000000007); unsigned a[1000005], FacInv[1000005], Inv[1000005], Up[1000005], Down[1000005], m, n; unsigned A, B, C, D, t; unsigned long long Ans(0); inline void Mn(unsigned& x) { x -= ((x >= Mod) ? Mod : 0); } inline void Mn(unsigned long long& x) { x -= ((x >= Mod) ? Mod : 0); } inline unsigned long long Pow(unsigned long long x, unsigned y) { unsigned long long Rt(1); while (y) { if (y & 1) Rt = Rt * x % Mod; x = x * x % Mod, y >>= 1; } return Rt; } signed main() { n = RD(), m = RD(); if (n <= (m + 1)) { for (unsigned i(1); i <= n; ++i) Mn(Ans += Pow(i, m)); printf("%llu\n", Ans); return Wild_Donkey; } for (unsigned i(0); i <= m; ++i) Mn(a[i + 1] = a[i] + Pow(i + 1, m)); ++m, Up[0] = Down[0] = FacInv[0] = Inv[0] = Inv[1] = 1; for (unsigned long long i(0); i < m; ++i) Up[i + 1] = Up[i] * (n - m + i) % Mod; for (unsigned long long i(0); i < m; ++i) Down[i + 1] = Down[i] * (n - i) % Mod; for (unsigned i(2); i <= m; ++i) Inv[i] = Inv[Mod % i] * (Mod - (Mod / i)) % Mod; for (unsigned i(1); i <= m; ++i) FacInv[i] = (unsigned long long)FacInv[i - 1] * Inv[i] % Mod; for (unsigned i(m); i < 0x3f3f3f3f; i -= 2) Ans = (Ans + ((((unsigned long long)Down[i] * Up[m - i] % Mod) * FacInv[i] % Mod) * FacInv[m - i] % Mod) * a[i]) % Mod; for (unsigned i(m - 1); i < 0x3f3f3f3f; i -= 2) Ans = (Ans + ((((unsigned long long)Down[i] * Up[m - i] % Mod) * FacInv[i] % Mod) * FacInv[m - i] % Mod) * (Mod - a[i])) % Mod; printf("%llu\n", Ans); return Wild_Donkey; }
|
有一个 n 次以内的多项式 f(x), 给 n+1 个点值, f(0),f(1),...,f(n). 求 f(m),f(m+1),...,f(m+n).
这个题 O(n2) 无法通过, 而且由于询问也是 n+1 个点值, 所以插出系数来也无法用正确的复杂度询问. 发现横坐标很特殊, xi=i, 询问也很特殊, 横坐标也是连续的, 所以这时拉格朗日多项式是这样的:
L(x)=xn+1i=0∑ni!(n−i)!(−1)n−i(x−i)f(i)
我们可以 O(n) 处理 i!(n−i)!(−1)n−if(i), 和对于所有 x 的 xn+1.
但是我们要想计算一个点值, 仍然需要 O(n) 枚举 i.
定义序列 Ai=i!(n−i)!(−1)n−if(i), Bi=m+i−n1, Ci=j=0∑iAjBi−j, 三个序列长度为 2(n+1), Ai=0 (i>n).
L(x)=xn+1Cx−m+n
我们用 O(nlogn) 卷积算出 C, 然后就可以 O(1) 查询点值.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| const unsigned long long Mod(998244353); unsigned long long C1[160005], IFac[160005], MFac[160005], A[530000], B[530000]; unsigned long long Tmp(1), w; unsigned m, n, N(1); unsigned D, t; unsigned Cnt(0), Ans(0); inline void Mn(unsigned long long& x) { x -= ((x >= Mod) ? Mod : 0); } inline unsigned long long W(unsigned x) { unsigned long long Now(3), Rt(1); while (x) { if (x & 1) Rt = Rt * Now % Mod; Now = Now * Now % Mod, x >>= 1; } return Rt; } inline unsigned long long Inv(unsigned long long x) { unsigned long long Rt(1); unsigned y(998244351); while (y) { if (y & 1) Rt = Rt * x % Mod;y >>= 1, x = x * x % Mod; } return Rt; } inline void DIT(unsigned long long* f) { unsigned long long Now(1), Tmpw[20]; unsigned Lg(0); Tmpw[0] = w; for (unsigned i(1); i < N; i <<= 1, ++Lg) Tmpw[Lg + 1] = Tmpw[Lg] * Tmpw[Lg] % Mod; --Lg; for (unsigned i(1); i < N; i <<= 1, --Lg) { for (unsigned j(0); j < N; ++j, Now = Now * Tmpw[Lg] % Mod) if (!(i & j)) { unsigned long long TmpA(f[j]), TmpB(f[j ^ i] * Now % Mod); f[j] = TmpA + TmpB, Mn(f[j]); f[j ^ i] = Mod + TmpA - TmpB, Mn(f[j ^ i]); } } } inline void DIF(unsigned long long* f) { unsigned long long Now(1); for (unsigned i(N >> 1); i; i >>= 1, w = w * w % Mod) { for (unsigned j(0); j < N; ++j, Now = Now * w % Mod) if (!(i & j)) { unsigned long long TmpA(f[j]), TmpB(f[j ^ i]); f[j] = TmpA + TmpB, Mn(f[j]); f[j ^ i] = (Mod + TmpA - TmpB) * Now % Mod; } } } signed main() { n = RD(), m = RD(); while (N <= n) { N <<= 1; } N <<= 1; for (unsigned i(0); i <= n; ++i) A[i] = RD(); for (unsigned i(1); i <= n; ++i) Tmp = Tmp * i % Mod; IFac[n] = Inv(Tmp); for (unsigned i(n - 1); ~i; --i) IFac[i] = IFac[i + 1] * (i + 1) % Mod; for (unsigned i(0); i <= n; ++i) A[i] = ((((n - i) & 1) ? (Mod - A[i]) : A[i]) * IFac[n - i] % Mod) * IFac[i] % Mod; C1[0] = 1; for (unsigned i(m - n); i <= m; ++i) C1[0] = C1[0] * i % Mod; for (unsigned i(1); i <= n; ++i) C1[i] = (C1[i - 1] * Inv(m - n + i - 1) % Mod) * (m + i) % Mod; for (unsigned i(n << 1); ~i; --i) B[i] = Inv(m + i - n); w = W(998244352 / N), DIF(A), w = W(998244352 / N), DIF(B); for (unsigned i(0); i < N; ++i) A[i] = A[i] * B[i] % Mod; w = Inv(W(998244352 / N)), DIT(A), w = Inv(N), N = (n << 1); for (unsigned i(n); i <= N; ++i) printf("%llu ", (C1[i - n] * A[i] % Mod) * w % Mod); putchar(0x0A); return Wild_Donkey; }
|