多项式插值
Keep away from polynomial. ---- Wild_Donkey
给 (x0,y0),(x1,y1),...,(xn,yn), 共 n+1 个点. 求一个 n 次 n+1 项的多项式 L, 使得多项式的图像过每一个点. 这个多项式 L 便是拉格朗日多项式 (Lagrange polynomial).
拉格朗日基本多项式 (插值基函数)
ℓi 是一个多项式, 满足 ℓ(xi)=1, 且 ℓ(xj)=0 (j≠i).
ℓi(x)=n∏j=0,j≠ix−xjxi−xj
这样当 x=xi 时, 所有 x−xjxi−xj 的分子等于分母. 当 x≠xi 时, 一定存在一个项分子为 0, 所以乘起来还是 0.
拉格朗日多项式
因为每个拉格朗日基本多项式 ℓi 在除了 xi 点以外的给定的横坐标上都是 0, 所以我们把所有拉格朗日多项式加权求和就能得到符合要求的多项式 L.
L(x)=n∑i=0yiℓi(x)L(x)=n∑i=0yin∏j=0,j≠ix−xjxi−xj
重心拉格朗日插值法
为了方便计算, 预处理一个 n+1 次多项式 ℓ:
ℓ(x)=n∏i=0(x−xi)
这个多项式可以进行 O(n) 次耗时 O(n) 的 O(n) 项式和二项式乘法得到, 总耗时 O(n2).
定义重心权 wi:
wi=1n∏j=0,j≠i(xi−xj)
这样就可以得到:
ℓi(x)=ℓ(x)wix−xiL(x)=n∑i=0ℓ(x)wiyix−xiL(x)=ℓ(x)n∑i=0wiyix−xi
我们可以 O(n) 地算出 wiyi, 然后 O(n) 地算出 ℓ(x)x−xi, 在除法过程中就可以直接统计 ℓi 堆 L 的贡献. 所以在预处理之后, 计算 L(x) 需要 O(n2) 的时间.
代码实现
模板题
代码还是很好理解的, 只需要把前面的式子用程序实现就可以了.
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 都乘以 1xi−xn+1. 还会使 L(x) 加上 ℓ′(x)wn+1yn+1x−xn+1.
L′(x)=ℓ′(x)(n+1∑i=0w′iyix−xi)
我们用 O(n) 处理出 w′i (i≤n), 然后 O(n) 直接算出 w′n+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)n∑i=0wix−xi
因为任何数除以 1 还是它本身, 所以 L(x)=L(x)g(x). 将 L(x), g(x) 都用拉格朗日插值公式表示, 则得到:
L(x)=L(x)g(x)=ℓ(x)n∑i=0wiyix−xiℓ(x)n∑i=0wix−xi=n∑i=0wiyix−xin∑i=0wix−xi
我们可以只维护 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]=f[xi+1,...,xi+j]−f[xi,...,xi+j−1]xi+j−xi
均差有前向和后向之分, 一般只会用到前向均差, 后向均差是把元素顺序倒过来写, 计算上也有一部分需要翻转.
我们可以递推地在 O(n2) 的时间内求出 n 个点的序列的每个子串的均差.
均差的展开形式是这样的:
f[xl,xl+1,...,xr]=r∑i=lf(xi)r∏j=l,j≠ixi−xj
用归纳法证明, 单点的均差符合 f[xl]=f(xl)1=f(xl). 假设大小小于 r−l+1 的点集都满足这个式子, 验证大小为 r−l+1 的点集是否满足.
f[xl,xl+1,...,xr]=f[xi+1,...,xi+j]−f[xi,...,xi+j−1]xi+j−xif[xl,xl+1,...,xr]=r∑i=l+1f(xi)r∏j=l+1,j≠ixi−xj−r−1∑i=lf(xi)r−1∏j=l,j≠ixi−xjxr−xlf[xl,xl+1,...,xr]=r−1∑i=l+1f(xi)r∏j=l+1,j≠ixi−xj−r−1∑i=l+1f(xi)r−1∏j=l,j≠ixi−xj+f(xr)r−1∏j=l+1xr−xj−f(xl)r−1∏j=l+1xl−xjxr−xlf[xl,xl+1,...,xr]=r−1∑i=l+1f(xi)r∏j=l+1,j≠ixi−xj−r−1∑i=l+1f(xi)r−1∏j=l,j≠ixi−xjxr−xl+f(xr)r−1∏j=lxr−xj+f(xl)r∏j=l+1xl−xjf[xl,xl+1,...,xr]=r−1∑i=l+1f(xi)r∏j=l+1,j≠ixi−xj−f(xi)r−1∏j=l,j≠ixi−xjxr−xl+f(xr)r−1∏j=lxr−xj+f(xl)r∏j=l+1xl−xjf[xl,xl+1,...,xr]=r−1∑i=l+1f(xi)xi−xr−f(xi)xi−xlr−1∏j=l+1,j≠ixi−xjxr−xl+f(xr)r−1∏j=lxr−xj+f(xl)r∏j=l+1xl−xjf[xl,xl+1,...,xr]=r−1∑i=l+1f(xi)((xi−xl)−(xi−xr))(xi−xr)(xi−xl)r−1∏j=l+1,j≠ixi−xjxr−xl+f(xr)r−1∏j=lxr−xj+f(xl)r∏j=l+1xl−xjf[xl,xl+1,...,xr]=r−1∑i=l+1f(xi)(xr−xl)(xi−xr)(xi−xl)r−1∏j=l+1,j≠ixi−xjxr−xl+f(xr)r−1∏j=lxr−xj+f(xl)r∏j=l+1xl−xjf[xl,xl+1,...,xr]=r−1∑i=l+1f(xi)(xi−xr)(xi−xl)r−1∏j=l+1,j≠ixi−xj+f(xr)r−1∏j=lxr−xj+f(xl)r∏j=l+1xl−xjf[xl,xl+1,...,xr]=r−1∑i=l+1f(xi)r∏j=l,j≠ixi−xj+f(xr)r−1∏j=lxr−xj+f(xl)r∏j=l+1xl−xjf[xl,xl+1,...,xr]=r∑i=lf(xi)r∏j=l,j≠ixi−xj
展开式得证.
牛顿基本多项式
设多项式 ni(x) 满足在 x0,x1,...,xj−1 处都取 0 的多项式. 显然有:
ni(x)=i−1∏j=0(x−xj)
牛顿多项式
我们只要给每个牛顿基本多项式加权求和, 就能构造一个 n 次多项式, 过全部 n+1 个点. 这个总和便是牛顿多项式 N(x).
假设给 ni(x) 加的权为 ai, 写出定义式:
N(x)=n∑i=0aini(x)=n∑i=0aii−1∏j=0(x−xj)
因为 n+1 个横坐标不同的点可以唯一地确定一个 n 次多项式, 因此相同点集满足 L(x)=N(x). 我们可以用拉格朗日插值公式来求 a. 已经求出已经插了 (x0,y0),...,(xn−1,yn−1) 的拉格朗日多项式 L(x). 我们尝试给 an 赋值, 使得 L(xn)+annn(xn) 为 yn.
yn=L(xn)+annn(xn)yn=n−1∑i=0yin−1∏j=0,j≠ixn−xjxi−xj+ann−1∏i=0(xn−xi)ann−1∏i=0(xn−xi)=yn−n−1∑i=0yin−1∏j=0,j≠ixn−xjxi−xjan=ynn−1∏i=0(xn−xi)−n−1∑i=0yixn−xin−1∏j=0,j≠i1xi−xjan=ynn−1∏i=0(xn−xi)+n−1∑i=0yixi−xnn−1∏j=0,j≠i1xi−xjan=ynn−1∏i=0(xn−xi)+n−1∑i=0yin∏j=0,j≠i1xi−xjan=ynn−1∏i=0(xn−xi)+n−1∑i=0yin∏j=0,j≠ixi−xjan=n∑i=0yin∏j=0,j≠ixi−xj
我们发现 an=n∑i=0yin∏j=0,j≠ixi−xj=[y0,y1,...,yn].
所以牛顿多项式便是:
N(x)=n∑i=0aini(x)=n∑i=0aii−1∏j=0(x−xj)=n∑i=0[y0,y1,...,yi]i−1∏j=0(x−xj)=n∑i=0(i∑j=0yji∏k=0,k≠jxj−xk)i−1∏j=0(x−xj)
例题
有一个 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, 询问也很特殊, 横坐标也是连续的. 我们需要求出所以先把横坐标往插值公式里面带. 首先化简 n∏j=0,j≠i(i−j).
n∏j=0,j≠i(i−j)=(i−1∏j=0(i−j))(n∏j=i+1(i−j))=(i−1∏j=0(i−j))(n∏j=i+1(j−i))(−1)n−i=i!(n−i)!(−1)n−i
如果默认 x 为自然数且 x>n, 还可以化简 n∏i=0(x−i)=x!(x−n−1)!.
L(x)=n∑i=0x!f(i)(x−n−1)!i!(n−i)!(−1)n−i(x−i)=x!(x−n−1)!n∑i=0f(i)i!(n−i)!(−1)n−i(x−i)=x!(x−n−1)!n∑i=0f(i)i!(n−i)!(−1)n−i1x−i
我们可以 O(n) 处理 f(i)i!(n−i)!(−1)n−i, 和对于所有 x 的 x!(x−n−1)!. 注意 x 较大, 无法直接计算 x!, 所以我们可以先把 x−n 到 x 的数字相乘, 得到 x!(x−n−1)! 然后枚举 x 来递推.
但是我们要想计算一个点值, 仍然需要 O(n) 枚举 i.
定义序列 Ai=f(i)i!(n−i)!(−1)n−i, Bi=1m+i−n, Ci=i∑j=0AjBi−j, 三个序列长度为 2(n+1), Ai=0 (i>n).
L(x)=x!(x−n−1)!Cx−m+n
我们用 O(nlogn) 卷积算出 C, 然后就可以 O(1) 查询点值.
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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
2021-02-20 校内测试-NOIP模拟赛
2020-02-20 ybt1226 装箱问题