[學習筆記]拉格朗日插值


拉格朗日插值法(圖文詳解)

自我感覺挺實用的一個算法。

也為一些題目提供了解決的思路。

插值:給一些散點,求滿足這些個散點的函數(多項式),即求出這些系數

一般求一個點值,都要先得到系數,再O(n)算。求系數,高斯消元,是O(n^3)的。

但是,如果只要一個點值,這樣豈不是血虧。

拉格朗日這個人比較厲害,他發明的算法,可以在不用求出具體系數的情況下,O(n^2)的計算一個位置的點值。

思想類似於互質的CRT,

對於給定n+1個點值,這個多項式最多n次的。而且,把每個橫坐標帶進去,xi自己的一項得到yi,別的由於分子有xi-xi,都是0

所以,這個多項式一定和實際上的多項式是一個多項式。

然后我們把要求的x帶進去,就得到了函數值。

 

有什么用?

如果證明一個式子的函數是n次多項式的話,那么可以嘗試得到n+1個點值,然后弄出這個公式,就可以計算比較大的答案。

https://blog.csdn.net/xyz32768/article/details/81233900

這個題,很大數據范圍的k次方和,第一沒有辦法反演。第二沒有規律可以找。

猜這個求和函數是一個k+1次多項式。然后帶點求值,然后對目標答案的計算進行化簡。

從O(n)到O(k^2)到O(klogk)(然鵝這個logk是因為點值的快速冪,后面的計算不是瓶頸23333)

突破口

1.想到是一個多項式

2.點值的取值是有講究的,1~k+2這樣連續的整點有助於預處理減少復雜度(跟自己干嘛要過不去23333)

所以,拉格朗日插值這個公式其實很整齊,

如果點值橫坐標給的很好的話(支持遞推),那么可以在O(n)時間求出一個值。已經非常不錯了。

 

CF622F The Sum of the k-th Powers

代碼:

#include<bits/stdc++.h>
#define reg register int
#define il inline
#define numb (ch^'0')
using namespace std;
typedef long long ll;
il void rd(int &x){
    char ch;x=0;bool fl=false;
    while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);
    (fl==true)&&(x=-x);
}
namespace Miracle{
const int N=1e6+5;
const int mod=1e9+7;
int n,k;
int qm(int x,int y){
    int ret=1;
    while(y){
        if(y&1) ret=(ll)ret*x%mod;
        x=(ll)x*x%mod;
        y>>=1;
    }
    return ret;
}
ll pre[N],bac[N];
ll fu[N];
ll y[N];
ll sol(){
    ll ret=0;
    for(reg i=1;i<=k+2;++i){
        //cout<<" i "<<i<<" : "<<y[i]<<" "<<pre[i-1]<<" "<<pre[k+2-i]<<" "<<bac[k+2]<<" "<<n-i<<endl;
        if(n>k+2) ret=(ret+y[i]*qm(pre[i-1]*(fu[k+2-i])%mod,mod-2)%mod*(bac[k+2]*qm((n-i),mod-2)%mod)%mod)%mod;
        else {
         if(n!=i) ret=(ret+y[i]*qm(pre[i-1]*(fu[k+2-i])%mod,mod-2)%mod*(bac[k+2]*qm(((n-i)+mod)%mod,mod-2)%mod)%mod)%mod;
         else ret=(ret+y[i])%mod;
        } 
    //    cout<<" ret "<<ret<<endl;
    }
    return ret;
}
int main(){
    rd(n);rd(k);
    pre[0]=1;
    bac[0]=1;
    fu[0]=1;
    y[0]=0;
    for(reg i=1;i<=k+2;++i){
        pre[i]=pre[i-1]*i%mod;
        fu[i]=fu[i-1]*(mod-i)%mod;
        bac[i]=(bac[i-1]*(n-i)%mod+mod)%mod;
        y[i]=(y[i-1]+qm(i,k))%mod;
    }
    printf("%lld",sol());
    return 0;
}

}
signed main(){
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
   Date: 2019/1/16 15:31:40
*/
View Code

 

 


upda:2019.2.18

如果要找到真正的系數:

可以快速插值O(nlogn),非常難寫

一個比較實用的是O(N^2)的背包:

考慮拉格朗日插值的公式的每一個f(i)項對每個系數的貢獻

$f(i) \frac{\Pi_{j!=i}(x-x_j)}{\Pi_{j!=i}(x_i-x_j)}$

分母是定值,$f(i)$是定值

分子不同之間差不多,

計算:$\Pi(x-x_j)$再每次O(N)除以$(x-x_i)$

計算方法:

對每一項的貢獻就是選擇k個x,剩下n-k個選擇$-x_j$這樣

所以背包:$f[i][j]$表示前i個“括號”,x次冪是j的權值之和

$f[i][j]=f[i-1][j]*(-x_i)+f[i-1][j-1]$

O(N^2)

 

還有一種更容易實現的方法:

$f*(x-x_i)=f*x-f*x_i$也就是f平移一位,然后減掉自己原來的xi倍

遞推即可實現。

還原時候除法,就是倒着回退一次即可。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM