拉格朗日插值法小结
文章目錄
- 介紹
- 拉格朗日插值法
- 重心拉格朗日插值法
- 例題1
- 技巧
- 例題2
- 例題3
- 練習題
介紹
眾所周知,nnn 個點 (xi,yi)(x_i,y_i)(xi?,yi?) 可以確定唯一一個 n?1n-1n?1 次的多項式。
拉格朗日插值法就是,用這 nnn 個點表示出這個多項式。
拉格朗日插值法
考慮構造 nnn 個多項式,第 iii 個多項式 fi(x)f_i(x)fi?(x) 滿足:當 x=xix=x_ix=xi? 時,fi(x)=yif_i(x)=y_ifi?(x)=yi?,當 x=xj(j≠i)x=x_j(j\neq i)x=xj?(j?=i) 時,fi(x)=0f_i(x)=0fi?(x)=0。
然后將這 nnn 個多項式加起來,就能得到一個多項式 f(x)f(x)f(x),滿足當 x=xix=x_ix=xi? 時,f(x)=yif(x)=y_if(x)=yi?;并且這是個 n?1n-1n?1 次多項式。
fi(x)f_i(x)fi?(x) 的構造為:fi(x)=yi∏j≠ix?xjxi?xjf_i(x)=y_i\prod_{j\neq i} \dfrac {x-x_j} {x_i-x_j}fi?(x)=yi?∏j?=i?xi??xj?x?xj??。
加起來,得到 f(x)=∑i=1nyi∏j≠ix?xjxi?xjf(x)=\sum_{i=1}^ny_i\prod_{j\neq i}\dfrac {x-x_j} {x_i-x_j}f(x)=∑i=1n?yi?∏j?=i?xi??xj?x?xj??。
構造多項式 n2n^2n2,求解一次 n2n^2n2。
重心拉格朗日插值法
稍微改改上面那個柿子:
f(x)=∑i=1nyi∏j≠ix?xjxi?xjf(x)=\sum_{i=1}^n y_i\prod_{j\neq i}\dfrac {x-x_j} {x_i-x_j} f(x)=i=1∑n?yi?j?=i∏?xi??xj?x?xj??
設 h=∏i=1nx?xih=\prod_{i=1}^n x-x_ih=∏i=1n?x?xi?,帶入得:
=h∑i=1n∏j≠iyi(xi?xj)(x?xi)=h\sum_{i=1}^n \prod_{j\neq i}\dfrac {y_i} {(x_i-x_j)(x-x_i)} =hi=1∑n?j?=i∏?(xi??xj?)(x?xi?)yi??
設 ti=∏j≠iyixi?xjt_i=\prod_{j\neq i} \dfrac {y_i} {x_i-x_j}ti?=∏j?=i?xi??xj?yi??,帶入得:
=h∑i=1ntix?xi=h\sum_{i=1}^n \dfrac {t_i} {x-x_i} =hi=1∑n?x?xi?ti??
每次加入一個新點時,計算出它的 tit_iti?,并且更新別的點的 tit_iti?,時間復雜度 O(n)O(n)O(n),加入 nnn 個點就是 n2n^2n2。
求解時,O(n)O(n)O(n) 求 hhh,O(n)O(n)O(n) 求出那個 ∑\sum∑,時間復雜度 O(n)O(n)O(n)。
例題1
【模板】拉格朗日插值
寫法是重心拉格朗日插值法,雖然在這題會慢,但只是權當練習而已,無所謂了。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define mod 998244353int n,k; int ksm(int x,int y){int re=1;for(;(y&1?re=1ll*re*x%mod:0),y;y>>=1,x=1ll*x*x%mod);return re;} #define inv(x) ksm(x,mod-2) struct Lagrange{struct Point{int x,y,t;}d[2010];int tot=0;Lagrange(){}void insert(int x,int y){for(int i=1;i<=tot;i++)d[i].t=1ll*d[i].t*inv(d[i].x-x+mod)%mod;d[++tot]=(Point){x,y,y};for(int i=1;i<tot;i++)d[tot].t=1ll*d[tot].t*inv(x-d[i].x+mod)%mod;}int calc(int x){int prod=1,re=0;for(int i=1;i<=tot;i++){prod=1ll*prod*(x-d[i].x+mod)%mod;re=(re+1ll*d[i].t*inv(x-d[i].x+mod)%mod)%mod;}return 1ll*re*prod%mod;} }Lag;int main() {scanf("%d %d",&n,&k);for(int i=1,x,y;i<=n;i++){scanf("%d %d",&x,&y);Lag.insert(x,y);}printf("%d",Lag.calc(k)); }技巧
有時候我們遇到的 nnn 個點的 xxx 值是連續的,下面不妨當成 xi=ix_i=ixi?=i。
再看上面的柿子:
f(x)=∑i=1nyi∏j≠ix?xjxi?xj=∑i=1nyi∏j≠ix?ji?j\begin{aligned} f(x)&=\sum_{i=1}^ny_i\prod_{j\neq i}\dfrac {x-x_j} {x_i-x_j}\\ &=\sum_{i=1}^n y_i \prod_{j\neq i}\dfrac {x-j} {i-j} \end{aligned} f(x)?=i=1∑n?yi?j?=i∏?xi??xj?x?xj??=i=1∑n?yi?j?=i∏?i?jx?j??
預處理兩個數組:prei=∏j=1i(x?j),sufi=∏j=in(x?j)pre_i=\prod_{j=1}^i (x-j),suf_i=\prod_{j=i}^n (x-j)prei?=∏j=1i?(x?j),sufi?=∏j=in?(x?j),那么分子部分就是 prej?1×sufj+1pre_{j-1}\times suf_{j+1}prej?1?×sufj+1?。
而分母部分不難發現是個階乘,即 (i?1)!(n?i)!(?1)n?i(i-1)!(n-i)!(-1)^{n-i}(i?1)!(n?i)!(?1)n?i。
代入得到:
f(x)=∑i=1nyiprei?1×sufi+1(i?1)!(n?i)!(?1)n?if(x)=\sum_{i=1}^n y_i\frac {pre_{i-1}\times suf_{i+1}} {(i-1)!(n-i)!(-1)^{n-i}} f(x)=i=1∑n?yi?(i?1)!(n?i)!(?1)n?iprei?1?×sufi+1??
這樣在一頓預處理后也可以做到 O(n)O(n)O(n),但他的價值不止如此。
例題2
拉格朗日插值2
當 iii 從 000 開始計數時,上面那個柿子就變成了 f(x)=∑i=0nyiprei?1×sufi+1i!(n?i)!(?1)n?if(x)=\sum_{i=0}^n y_i\dfrac {pre_{i-1}\times suf_{i+1}} {i!(n-i)!(-1)^{n-i}}f(x)=∑i=0n?yi?i!(n?i)!(?1)n?iprei?1?×sufi+1??。
則 f(m+i)=∏j=0n(m+i?j)∑j=0nyjj!(n?j)!(?1)n?j×1m+i?jf(m+i)=\prod_{j=0}^n (m+i-j)\sum_{j=0}^n \dfrac {y_j} {j!(n-j)!(-1)^{n-j}}\times \dfrac 1 {m+i-j}f(m+i)=∏j=0n?(m+i?j)∑j=0n?j!(n?j)!(?1)n?jyj??×m+i?j1?。
前面的 ∏\prod∏ 易于處理,可以先不管,后面的 ∑\sum∑ 是個卷積的形式,但是 mmm 高達 10810^8108,直接卷不太行。
但實際上處理方法也很簡單,原來是設 Ai=yii!(n?i)!(?1)n?i,Bi=1iA_i=\dfrac {y_i} {i!(n-i)!(-1)^{n-i}},B_i=\dfrac 1 iAi?=i!(n?i)!(?1)n?iyi??,Bi?=i1?,注意到只有 [m?n,m+n][m-n,m+n][m?n,m+n] 區間內的 BBB 是有用的,不妨將 BBB 整體左移 m?nm-nm?n 位,即 Bi=1i+m?nB_i=\dfrac 1 {i+m-n}Bi?=i+m?n1?,那么 AAA 卷 BBB 得到的 CCC 中,Ci+nC_{i+n}Ci+n? 即 fm+if_{m+i}fm+i?。
代碼如下:
#include <cstdio> #include <cstring> #include <cmath> #include <algorithm> using namespace std; #define maxn 550010 #define mod 998244353 #define bin(x) (1<<(x))int n,m,A[maxn],B[maxn]; int ksm(int x,int y){int re=1;for(;(y&1?re=1ll*re*x%mod:0),y;y>>=1,x=1ll*x*x%mod);return re;} int fac[maxn],inv_fac[maxn],inv[maxn]; void FacInit(){fac[0]=inv_fac[0]=1;for(int i=1;i<=n;i++)fac[i]=1ll*fac[i-1]*i%mod;for(int i=1;i<=n;i++)inv_fac[i]=1ll*inv_fac[i-1]*inv[i]%mod; } int w[maxn];void prep(int lg){int N=bin(lg);inv[1]=1;for(int i=2;i<=maxn-10;i++)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;for(int i=1,wn;i<N;i<<=1){w[i]=1;wn=ksm(3,(mod-1)/(i<<1));for(int j=1;j<i;j++)w[i+j]=1ll*w[i+j-1]*wn%mod;} } int r[maxn],limit; void InitR(int lg){for(int i=1;i<bin(lg);i++)r[i]=(r[i>>1]>>1)|((i&1)<<(lg-1));} int add(int x){return x>=mod?x-mod:x;} int dec(int x){return x<0?x+mod:x;} void ntt(int *f,int lg,int type=0){limit=bin(lg);if(type)reverse(f+1,f+limit);for(int i=1;i<limit;i++)if(i<r[i])swap(f[i],f[r[i]]);for(int mid=1,t;mid<limit;mid<<=1)for(int j=0;j<limit;j+=(mid<<1))for(int i=0;i<mid;i++){t=1ll*f[j+i+mid]*w[mid+i]%mod;f[j+i+mid]=dec(f[j+i]-t);f[j+i]=add(f[j+i]+t);}if(type)for(int i=0;i<limit;i++)f[i]=1ll*f[i]*inv[limit]%mod; } void NTT(int *f,int *g,int ln){int lg=ceil(log2(ln));InitR(lg);ntt(f,lg);ntt(g,lg);for(int i=0;i<bin(lg);i++)f[i]=1ll*f[i]*g[i]%mod;ntt(f,lg,1); }int main() {scanf("%d %d",&n,&m);prep(ceil(log2((n+1)<<1)));FacInit();for(int i=0,x;i<=n;i++){scanf("%d",&x);A[i]=1ll*x*inv_fac[i]%mod*inv_fac[n-i]%mod;if(n-i&1)A[i]=(mod-A[i])%mod;}for(int i=0;i<=(n<<1);i++)B[i]=ksm(m-n+i,mod-2);NTT(A,B,2*n+1);int pd=1;for(int i=0;i<=n;i++)pd=1ll*pd*(m-i)%mod;for(int i=n;i<=(n<<1);i++){A[i]=1ll*A[i]*pd%mod;printf("%d ",A[i]);pd=1ll*pd*ksm(m+i-2*n,mod-2)%mod*(m+i-n+1)%mod;} }例題3
還有一個相當經典的應用:自然數冪求和,即給出 n,kn,kn,k,求 ∑i=1nik\sum_{i=1}^n i^k∑i=1n?ik。
這是一個關于 nnn 的 k+1k+1k+1 次多項式,證明可以看洛谷第一篇題解,寫的很好,大致思路是:一個 nnn 次多項式 f(x)f(x)f(x),假如有一個數列 f(1),f(2),f(3),...f(1),f(2),f(3),...f(1),f(2),f(3),...,將其差分后得到 f(2)?f(1),f(3)?f(2),...f(2)-f(1),f(3)-f(2),...f(2)?f(1),f(3)?f(2),... ,這個差分后的數列的每一項就變成了一個關于 iii 的 n?1n-1n?1 次多項式。而題中的數列即為 f(i)=∑j=1ijkf(i)=\sum_{j=1}^i j^kf(i)=∑j=1i?jk,做差后 Δf(i)=(i+1)k\Delta f(i)=(i+1)^kΔf(i)=(i+1)k,是一個關于 iii 的 kkk 次多項式,所以原數列是個關于 nnn 的 k+1k+1k+1 次多項式。
考慮拉格朗日插值法,那么需要找到 k+2k+2k+2 個點值,由于這里可以自己選取點,所以為了能套用上面的技巧,選取的點就是 (1,f(1)),(2,f(2)),...,(k+2,f(k+2))(1,f(1)),(2,f(2)),...,(k+2,f(k+2))(1,f(1)),(2,f(2)),...,(k+2,f(k+2))。
代入公式:
F(x)=∑i=1k+2f(i)prei?1×sufi+1(i?1)!(k+2?i)!(?1)k+2?iF(x)=\sum_{i=1}^{k+2} f(i)\frac {pre_{i-1}\times suf_{i+1}} {(i-1)!(k+2-i)!(-1)^{k+2-i}} F(x)=i=1∑k+2?f(i)(i?1)!(k+2?i)!(?1)k+2?iprei?1?×sufi+1??
再把 nnn 代入求值就做完了,時間復雜度 O(klog?k)O(k\log k)O(klogk)。
當然也可以做到線性:注意到 iki^kik 是個完全積性函數,即 ikjk=(ij)ki^kj^k=(ij)^kikjk=(ij)k,所以可以線性篩出來,那么就可以線性求 fff 了,然后階乘的逆元也是可以 O(k)O(k)O(k) 預處理的,這樣就做到了 O(k)O(k)O(k),但是比較麻煩所以下面的代碼還是寫的 O(klog?k)O(k\log k)O(klogk) 做法。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define maxn 1000010 #define mod 1000000007int n,k,ans=0; int ksm(int x,int y){int re=1;for(;(y&1?re=1ll*re*x%mod:0),y;y>>=1,x=1ll*x*x%mod);return re;} void add(int &x,int y){x=(x+y>=mod?x+y-mod:x+y);} void dec(int &x,int y){x=(x-y<0?x-y+mod:x-y);} int pre[maxn],suf[maxn],fac[maxn],inv_fac[maxn];int main() {scanf("%d %d",&n,&k);if(n<=k+2){for(int i=1;i<=n;i++)add(ans,ksm(i,k));}else{pre[0]=1;for(int i=1;i<=k+2;i++)pre[i]=1ll*pre[i-1]*(n-i)%mod;suf[k+3]=1;for(int i=k+2;i>=1;i--)suf[i]=1ll*suf[i+1]*(n-i)%mod;fac[0]=1;for(int i=1;i<=k+2;i++)fac[i]=1ll*fac[i-1]*i%mod;inv_fac[k+2]=ksm(fac[k+2],mod-2);inv_fac[0]=1;for(int i=k+1;i>=1;i--)inv_fac[i]=1ll*inv_fac[i+1]*(i+1)%mod;int sum=0;for(int i=1;i<=k+2;i++){add(sum,ksm(i,k));(k+2-i&1?dec:add)(ans,1ll*sum*pre[i-1]%mod*suf[i+1]%mod*inv_fac[i-1]%mod*inv_fac[k+2-i]%mod);}}printf("%d",ans); }練習題
都是些眾所周知的題,以后要是遇到什么有趣的也會放這兒的。
[TJOI2018]教科書般的褻瀆 ? 題解
tyvj 1858 XLkxc ? 題解
[NOIP2020] 微信步數 ? 題解
[集訓隊互測2012] calc ? 題解
[JLOI2016]成績比較 ? 題解
總結
- 上一篇: photoSwipe 结合jquery使
- 下一篇: 日常使用计算机过程中遇到,计算机日常使用