拉格朗日插值法是一個根據點對求回原函數的算法,原理挺好懂的。
推薦博客:https://www.cnblogs.com/ECJTUACM-873284962/p/6833391.html
https://www.cnblogs.com/zwfymqz/p/10063039.html#_label1_0
題目集合https://blog.csdn.net/qq_35649707/article/details/78018944#bzoj2655-calc
原理和優化方法上面的大佬都講得很好。
其實主要就是這個式子:
然后暴力算這個式子的話是每求一項f(k)的時間復雜度都是n^2。
這個時間很不優秀,於是我們想辦法在一些特殊時候優化它。
在x連續的時候我們可以通過預處理的方式加快速度,式子為
然后其實拉格朗日插值就上面兩個式子,具體題目的具體點對不一樣,但是插值的方法就是這樣。
然后重心拉格朗日插值法就是把朴素的式子變形,它的好處是
雖然原理好懂代碼好寫,但是做題並不好做,得看出所求函數是幾次多項式也得簡略證明一下,這要求有一定的數學素養。這一個知識點蒟蒻還是差得很多,基本上就是做一題看一題題解。主要是雖然拉格朗日插值法不難,也好寫。但是架不住我根本不知道要求的是哪個函數呀qwq qwq qwq,這樣的話初始點對都求不出來,那還插個球。。。
總的來說,一定不能有式子恐懼症,要多加訓練推式子的能力。加上這個知識點不怎么熟練,一定要回看。
題目練習:
洛谷P4781
模板題:

#include<bits/stdc++.h> using namespace std; typedef long long LL; const int N=2e3+10; const int MOD=998244353; int n,k,x[N],y[N]; int power(LL x,LL p,LL MOD) { LL ret=1; for (;p;p>>=1) { if (p&1) ret=ret*x%MOD; x=x*x%MOD; } return (int)ret; } int Lagrange() { int ret=0; for (int i=1;i<=n;i++) { int up=1,down=1; for (int j=1;j<=n;j++) { if (i==j) continue; up=(LL)up*((k-x[j])%MOD+MOD)%MOD; //分子 down=(LL)down*((x[i]-x[j])%MOD+MOD)%MOD; //分母 } int tmp=(LL)y[i]*up%MOD*power(down,MOD-2,MOD)%MOD; ret=(ret+tmp)%MOD; } return ret; } int main() { cin>>n>>k; for (int i=1;i<=n;i++) scanf("%d%d",&x[i],&y[i]); cout<<Lagrange()<<endl; return 0; }
洛谷P4593
看大佬題解,主要矛盾就是算sigma(i^m) (i=1~m+1) 這個用拉格朗日插值法可以解決。

#include<bits/stdc++.h> using namespace std; typedef long long LL; const int N=100; const int P=1e9+7; int n,m; LL jc[N],pre[N],suf[N],a[N],y[N]; void prework() { jc[0]=1; for (int i=1;i<=60;i++) jc[i]=(jc[i-1]*i)%P; } LL power(LL x,LL p,LL MOD) { LL ret=1; for (;p;p>>=1) { if (p&1) ret=ret*x%MOD; x=x*x%MOD; } return ret; } //拉格朗日插值計算:sigma(i^m) (i=1~m+1) 因為計算m項要用m+1個點 LL Lagrange(int n) { LL ret=0; pre[0]=n; for (int i=1;i<=m+1;i++) pre[i]=(pre[i-1]*(n-i))%P; //預處理分子前綴 suf[m+2]=1; for (int i=m+1;i;i--) suf[i]=(suf[i+1]*(n-i))%P; //預處理分母后綴 for (int i=1;i<=m+1;i++) { //m+1個點插點 LL up=pre[i-1]*suf[i+1]%P; LL down=jc[i]*jc[m+1-i]%P; if ((m+1-i)%2==1) down=P-down; //注意,(m+1-i)奇數項分母取反 LL tmp=up*power(down,P-2,P)%P*y[i]%P; ret=(ret+tmp)%P; } return ret; } int main() { prework(); int T; cin>>T; while (T--) { scanf("%d%d",&n,&m); for (int i=1;i<=m;i++) scanf("%d",&a[i]); a[++m]=++n; sort(a+1,a+m+1); for (int i=1;i<=m+2;i++) y[i]=power(i,m,P); for (int i=1;i<=m+2;i++) y[i]=(y[i]+y[i-1])%P; //算出(x,y)點對 LL ans=0; for (int i=1;i<=m;i++) { //計算ans for (int j=i;j<=m;j++) ans=(ans+Lagrange(a[j]-1)-Lagrange(a[j-1]))%P,ans=(ans+P)%P; for (int j=i+1;j<=m;j++) a[j]=a[j]-a[i]; a[i]=0; } printf("%lld\n",ans); } return 0; }
BZOJ-3453
慚愧的說,這題我現在還是不能完全理解為什么就能表示成K+4次多項式。但是其實能看出來的話也是直接插值就可以了。

#include<bits/stdc++.h> using namespace std; typedef long long LL; const LL P=1234567891; const LL N=200+10; LL k,a,n,d; LL f[N],g[N]; LL power(LL x,LL p,LL MOD) { LL ret=1; for (;p;p>>=1) { if (p&1) ret=ret*x%MOD; x=x*x%MOD; } return ret; } //計算k項式(i,f[i])的第n項 LL Lagrange(LL *f,LL k,LL n) { LL ret=0; for (int i=0;i<=k;i++) { LL up=f[i],down=1; for (int j=0;j<=k;j++) { if (i==j) continue; up=up*((n-j+P)%P)%P; down=down*((i-j+P)%P)%P; } ret=(ret+up*power(down,P-2,P)%P)%P; } return ret; } int main() { int T; cin>>T; while (T--) { memset(f,0,sizeof(f)); memset(g,0,sizeof(g)); scanf("%lld%lld%lld%lld",&k,&a,&n,&d); for (int i=1;i<=k+4;i++) f[i]=power(i,k,P); for (int i=1;i<=k+4;i++) f[i]=(f[i-1]+f[i])%P; for (int i=1;i<=k+4;i++) f[i]=(f[i-1]+f[i])%P; for (int i=0;i<=k+4;i++) g[i]=((i>0?g[i-1]:0)+Lagrange(f,k+4,a+i*d))%P; printf("%lld\n",Lagrange(g,k+4,n)); } return 0; }