引言:
什么是拉格朗日插值?假設我們現在有三個點 \((x_1,y_1),(x_2,y_2),(x_3,y_3)\),現在我們要找一條唯一的二次曲線剛好經過這三個點。
拉格朗日給出了一個絕妙的方法,他把我們要求的曲線的表達式等同於三個函數的累加。具體是這么操作的:
第一個函數保證\(f_1(x_1)=1,f_1(x_2)=f_1(x_3)=0\)
第二個函數保證\(f_2(x_2)=1,f_2(x_1)=f_2(x_3)=0\)
第三個函數保證\(f_3(x_3)=1,f_3(x_1)=f_3(x_2)=0\)
那么我們所要求的函數即為:
可以保證的是這個函數同時經過\((x_1,y_1),(x_2,y_2),(x_3,y_3)\)並且是唯一的滿足條件的二次函數。
公式:
如果上面的部分你看懂了,那么你已經掌握了拉格朗日插值的用法和思想。接下來我們要做的就是尋找一個公式使得利用現在已有的\(n\)個點,來推導出\(n-1\)次的函數。
那么這個函數為:
實現:
一般情況下拉格朗日插值的復雜度是\(O(n^2)\),即:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+100;
typedef long long ll;
const ll mod = 998244353;
struct point{
ll x,y;
}p[N];
int n,k;
ll qpow(ll a,ll b,ll mod){
ll ans=1;
while(b){
if(b&1){
ans=(ans%mod*a%mod)%mod;
}
a=(a%mod*a%mod)%mod;
b>>=1;
}
return ans%mod;
}
ll Lagrange(int k){
ll ans=0;
for(int j=1;j<=n;j++){//
ll base1=1;
ll base2=1;
for(int i=1;i<=n;i++){//lj(k)基函數
if(j==i) continue;
base1=(base1%mod*((k-p[i].x)%mod+mod)%mod)%mod;
base2=(base2%mod*((p[j].x-p[i].x)%mod+mod)%mod)%mod;
}
ans=(ans%mod+(p[j].y%mod*base1%mod*qpow(base2,mod-2,mod)%mod)%mod)%mod;
}
return ans;
}
int main(){
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>p[i].x>>p[i].y;
cout<<Lagrange(k)<<endl;
return 0;
}
如果已知的坐標是連續的話,那么我們可以通過預處理使得復雜度變為\(O(n)\),代碼以codeforces 622F為例。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+100;
typedef long long ll;
const ll mod = 1e9+7;
ll p[N],x[N],s1[N],s2[N],ifac[N];
ll qpow(ll a,ll b){
ll ans=1;
while(b){
if(b&1) ans=(ans%mod*a%mod)%mod;
a=(a%mod*a%mod)%mod;
b>>=1;
}
return (ans%mod+mod)%mod;
}
//拉格朗日插值,n項,每個點的坐標為(x_i,y_i),求第xi項的值,保證x是連續的一段
ll lagrange(ll n, ll *x, ll *y, ll xi) {
ll ans = 0;
s1[0] = (xi-x[0])%mod, s2[n+1] = 1;
for (ll i = 1; i <= n; i++) s1[i] = 1ll*s1[i-1]*(xi-x[i])%mod;
for (ll i = n; i >= 0; i--) s2[i] = 1ll*s2[i+1]*(xi-x[i])%mod;
ifac[0] = ifac[1] = 1;
for (ll i = 2; i <= n; i++) ifac[i] = -1ll*mod/i*ifac[mod%i]%mod;
for (ll i = 2; i <= n; i++) ifac[i] = 1ll*ifac[i]*ifac[i-1]%mod;
for (ll i = 0; i <= n; i++)
(ans += 1ll*y[i]*(i == 0 ? 1 : s1[i-1])%mod*s2[i+1]%mod
*ifac[i]%mod*(((n-i)&1) ? -1 : 1)*ifac[n-i]%mod) %= mod;
return (ans+mod)%mod;
}
int main(){
ll n,k;
cin>>n>>k;
if(k==0){
cout<<n<<endl;
return 0;
}
p[0]=0;
for(ll i=1;i<=k+2;i++) p[i]=(p[i-1]%mod+qpow(i,k))%mod;
for(ll i=1;i<=k+2;i++) x[i]=i;
if(n<=k+2){
cout<<p[n]<<endl;
}
else{
cout<<lagrange(k+2,x,p,n)<<endl;
}
return 0;
}
總結:
其實拉格朗日插值在算法競賽中主要用於數據分析,即對於給定的某些關系構造出若干已知點,然后利用這些已知點去計算通項公式。