【算法讲14:拉格朗日插值】拉格朗日插值入门 与 拉格朗日插值差分法

【算法讲14:拉格朗日插值】

  • 前言
  • 引入
  • ⌈ \lceil 拉格朗日插值 ⌋ \rfloor
  • 代码
  • ⌈ \lceil 拉格朗日插值 ⌋ \rfloor 差分法运用
  • 代码

前言

  • 拉格朗日插值可以出的很难,对于一个 n n n 阶多项式,可以优化成 O ( n log ⁡ n ) O(n\log n) O(nlogn)
    但是需要 F F T FFT FFT 等知识点,博主比较菜就只能搞搞入门的了 (哭)
  • 基础的高斯消元法可以 O ( n 3 ) O(n^3) O(n3) 求解 n n n 阶多项式,但是貌似不够用呀
    这时就出现了 O ( n 2 ) O(n^2) O(n2)拉格朗日插值!
    对于能求出 f ( i ) , ∀ i ∈ [ 1 , n ] f(i),\forall i\in[1,n] f(i),i[1,n],我们用 差分法 还可以化简到 O ( n ) O(n) O(n)

引入

  • 【题目描述】【模板】拉格朗日插值 | 洛谷 P4781
    n n n 个不同点,给定他们的坐标 ( x i , y i ) (x_i,y_i) (xi,yi)
    由于他们的横坐标都不相同,因此他们可以唯一确定一个 n − 1 n-1 n1 阶的多项式 P ( x ) P(x) P(x)
    给定 k k k ,你的任务是求出 P ( k ) P(k) P(k) 取模 998244353 998244353 998244353
  • 【数据范围】
    1 ≤ n ≤ 2 × 1 0 3 1\le n\le 2\times10^3 1n2×103
    1 ≤ x i , y i , k < 998244353 1\le x_i,y_i,k<998244353 1xi,yi,k<998244353

⌈ \lceil 拉格朗日插值 ⌋ \rfloor

  • 我们知道多项式可以表示为 P ( x ) = ∑ a i x i P(x)=\sum a_ix^i P(x)=aixi
    很神奇的,我们想构造出一个函数来表示这个 P ( x ) P(x) P(x)
  • 我们设一个函数:
    l i ( x ) = ∏ j = 0 , j ≠ i n x − x j x i − x j l_i(x)=\prod_{j=0,j\ne i}^n\cfrac{x-x_j}{x_i-x_j} li(x)=j=0,j=inxixjxxj
    这个函数有什么性质呢?有一下两个性质:
    (1) ∀ i ∈ [ 0 , n ] , l i ( x i ) = 1 \forall i\in[0,n],l_i(x_i)=1 i[0,n]li(xi)=1
    (2) ∀ i , j ∈ [ 0 , n ] , i ≠ j , l i ( x j ) = 0 \forall i,j\in[0,n],i\ne j,l_i(x_j)=0 i,j[0,n],i=jli(xj)=0
    接下来,我们就能直接构造出 P ( x ) P(x) P(x)了:
    P ( x ) = ∑ i = 0 n y i l i ( x ) P(x)=\sum_{i=0}^n y_il_i(x) P(x)=i=0nyili(x)
    为啥这个多项式保证是正确的呢?因为我们有:
    ∀ i ∈ [ 0 , n ] , P ( x i ) = y i \forall i\in[0,n],P(x_i)=y_i i[0,n]P(xi)=yi,保证成立。

代码

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
const int MAX = 2e3+50;
const ll  MOD = 998244353;

ll qpow(ll a,ll n){
     /* */ll res = 1LL;while(n){
     if(n&1)res=res*a%MOD;a=a*a%MOD;n>>=1;}return res;}
ll inv(ll a){
     /* */return qpow(a,MOD-2);}

ll X[MAX],Y[MAX];

int main()
{
     
    int n;ll k;
    cin >> n >> k;
    for(int i = 0;i < n;++i){
     
        cin >> X[i] >> Y[i];
    }
    ll res = 0;
    for(int i = 0;i < n;++i){
     
        ll num = Y[i];
        ll den = 1;
        for(int j = 0;j < n;++j){
     
            if(i != j){
     
                num = num * (k - X[j]) % MOD;
                den = den * (X[i] - X[j]) % MOD;
            }
        }
        num = (num + MOD) % MOD;
        den = (den + MOD) % MOD;

        res = (res + num % MOD * inv(den) % MOD) % MOD;
    }
    cout << res;
    return 0;
}

⌈ \lceil 拉格朗日插值 ⌋ \rfloor 差分法运用

  • 【题目描述】The Sum of the k-th Powers | CF 622F
    给定 n 、 k n、k nk,求:
    ∑ i = 1 n i k \sum_{i=1}^ni^k i=1nik
  • 【数据范围】
    1 ≤ n ≤ 1 0 9 1\le n\le 10^9 1n109
    1 ≤ k ≤ 1 0 6 1\le k\le 10^6 1k106
  • 【思路】
    这下阶为 1 e 6 1e6 1e6了, O ( k 2 ) O(k^2) O(k2) 已经不适用了。
    我们如果算出前面一些 x i x_i xi y i y_i yi,想快速算 l i ( n ) l_i(n) li(n),怎么办呢?
    如果我们令 x i = i x_i=i xi=i,那么式子会变成下面的样子:
    l i ( n ) = ∏ j = 1 , j ≠ i k n − j i − j l_i(n)=\prod_{j=1,j\ne i}^k\cfrac{n-j}{i-j} li(n)=j=1,j=ikijnj
    (1)对于分子,我们只要预处理出一个前缀积和后缀积,即可快速算。
    p r e ( i ) = ∏ j = 1 i n − j pre(i)=\prod_{j=1}^i n-j pre(i)=j=1inj s u f ( i ) = ∏ j = i k n − j suf(i)=\prod_{j=i}^k n-j suf(i)=j=iknj
    那么分子就是 p r e ( i − 1 ) × s u f ( i + 1 ) pre(i-1)\times suf(i+1) pre(i1)×suf(i+1)
    (2)对于分母,容易想到就是一个 断开的阶乘,就是 f a c [ i − 1 ] × f a c [ k − i ] fac[i-1]\times fac[k-i] fac[i1]×fac[ki]
    不过要注意一下,因为当 j > i j>i j>i 时,后面每一项都为负数。若 k − i k-i ki是一个奇数,那么后面会多一个负号。
    P ( n ) = ∑ i = 1 k y i × p r e ( i − 1 ) × s u f ( i + 1 ) f a c [ i ] × f a c [ k − i ] × ( − 1 ) k − i P(n)=\sum_{i=1}^ky_i\times\cfrac{pre(i-1)\times suf(i+1)}{fac[i]\times fac[k-i]}\times(-1)^{k-i} P(n)=i=1kyi×fac[i]×fac[ki]pre(i1)×suf(i+1)×(1)ki
    这下,时间复杂度变为线性的了!

代码

  • 342 M s / 2000 M s 342Ms/2000Ms 342Ms/2000Ms
    时间复杂度: O ( k ) O(k) O(k)
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
const int MAX = 1e6+50;
const ll  MOD = 1e9+7;

ll qpow(ll a,ll n){
     /* */ll res = 1LL;while(n){
     if(n&1)res=res*a%MOD;a=a*a%MOD;n>>=1;}return res;}
ll inv(ll a){
     /* */return qpow(a,MOD-2);}


ll Y[MAX];
ll fac[MAX];
ll ivfac[MAX];
ll pre[MAX],suf[MAX];
void init(ll n,ll k){
     
    fac[0] = 1;
    pre[0] = 1;
    for(int i = 1;i <= k;++i){
     
        fac[i] = (fac[i-1] * i) % MOD;
        pre[i] = pre[i - 1] * (n - i) % MOD;
    }
    ivfac[k] = inv(fac[k]);
    suf[k+1] = 1;			/// 注意一下,不存在的话设为 1
    suf[k] = n - k;
    for(int i = k - 1;i >= 0;--i){
     
        ivfac[i] = ivfac[i + 1] * (i + 1) % MOD;
        suf[i] = suf[i + 1] * (n - i) % MOD;
    }
}
int main()
{
     
    ll n,k;
    cin >> n >> k;
    k += 2;			/// 因为是 k+2 阶的多项式
    init(n,k);
    ll sum = 0;
    for(int i = 1;i <= k;++i){
     
        sum  = (sum + qpow(i,k - 2)) % MOD;
        Y[i] = sum;
        if(n == i){
     
            cout << sum;
            return 0;
        }
    }
    ll res = 0;
    for(int i = 1;i <= k;++i){
     
        ll num = Y[i] * pre[i - 1] % MOD * suf[i + 1] % MOD;
        ll den = ivfac[i - 1] * ivfac[k - i] % MOD;
        if((k - i) % 2 == 1)den = -den;
        res = (res + num * den) % MOD;
    }
    res = (res + MOD) % MOD;
    cout << res;
    return 0;
}

你可能感兴趣的:(【算法/知识点,浅谈】,拉格朗日插值)