算法学习之:拉格朗日插值

算法学习之:拉格朗日插值

定义

对某个k次多项式 fk(n) f k ( n ) 函数,已知有给定的k + 1个点:
(x0,y0),,(xk,yk) ( x 0 , y 0 ) , … , ( x k , y k )
假设任意两个不同的 xj x j 都互不相同,那么应用拉格朗日插值公式所得到的拉格朗日插值多项式为:
L(x)=j=0kyjj(x) L ( x ) = ∑ j = 0 k y j ℓ j ( x )
其中每个 j(x) ℓ j ( x ) 拉格朗日基本多项式(或称插值基函数),其表达式为:
j(x):=i=0,ijkxxixjxi=(xx0)(xjx0)(xxj1)(xjxj1)(xxj+1)(xjxj+1)(xxk)(xjxk). ℓ j ( x ) := ∏ i = 0 , i ≠ j k x − x i x j − x i = ( x − x 0 ) ( x j − x 0 ) ⋯ ( x − x j − 1 ) ( x j − x j − 1 ) ( x − x j + 1 ) ( x j − x j + 1 ) ⋯ ( x − x k ) ( x j − x k ) .
拉格朗日基本多项式 j(x) ℓ j ( x ) 的特点是在 xj x j 上取值为1,在其它的点 xi,ij x i , i ≠ j 上取值为0。
这样对于每个L(x),它必定过所有 (x0,y0),,(xk,yk) ( x 0 , y 0 ) , … , ( x k , y k )

伪代码

double Lagrange(double *ax, double *ay, int n, int x) {
    double ret = 0;
    for(int i = 1;i <= n; ++i) {
        double t = 1;
        for(int j = 0;j <= n; ++j)
            if(i != j)
            t = t * (x- ax[j]) / (ax[i] - ax[j]);
       }
       ret += t;
   }
   return ret;
}

应用

在实际的OI竞赛中,选手常常要自己带值插值,所以我们一般选择 (0,fk(0)),,(k,fk(k)) ( 0 , f k ( 0 ) ) , … , ( k , f k ( k ) ) ,带入L(x)有
fk(n)=L(n)=k+1j=0fk(j)n(n1)(nj+1)(nj1)(nk1)(1)k+1jj!(k+1j)! f k ( n ) = L ( n ) = ∑ j = 0 k + 1 f k ( j ) n ( n − 1 ) … ( n − j + 1 ) ( n − j − 1 ) … ( n − k − 1 ) ( − 1 ) k + 1 − j j ! ( k + 1 − j ) !
于是我们可以预处理阶乘等来使得时间复杂度变成O(k)的
缺点就是对模数有要求。

伪代码

long long Lagrange(long long *a, int n, long long pos) {
    if(pos <= n) return a[pos];
    s1[0] = s2[n + 1] = 1;
    for(int i = 1;i <= n; ++i) s1[i] = s1[i - 1] * (pos - i) % p;
    for(int i = n; i; --i) s2[i] = s2[i + 1] * (pos - i) % p;
    long long ans = 0;
    for(int i = 1;i <= n; ++i) 
        up(ans, a[i] * s1[i - 1] % p * s2[i + 1] % p * inv[i - 1] % p * 
        inv[n - i] % p * ((n - i) & 1 ? -1 : 1));
    return ans;
}

例题

题目链接

bzoj3453: tyvj 1858 XLkxc

分析

f(n)=i=0nj=1a+idl=1jlk f ( n ) = ∑ i = 0 n ∑ j = 1 a + i d ∑ l = 1 j l k
我们令 g(n)=j=1nl=1jlk g ( n ) = ∑ j = 1 n ∑ l = 1 j l k
那么有 f(n)=i=0ng(a+id) f ( n ) = ∑ i = 0 n g ( a + i d )
有一个神奇的引理

引理: fk(n)=i=1nik f k ( n ) = ∑ i = 1 n i k 必定能用k+1次多项式函数表示

那么g(n)就是k+2次多项式,f(n)就是k+4次多项式
然而我不会。

小技巧

有一种神奇的方法,叫做差分。
首先把值带入,然后差分,差分第k次为零,那么它就是k-1次多项式。

for(int i = 1;i <= k + 10; ++i) g[i] = mpower(i, k);
        for(int i = 2;i <= k + 10; ++i) up(g[i], g[i - 1]);
        for(int i = 2;i <= k + 10; ++i) up(g[i], g[i - 1]);
        for(int i = 1;i <= k + 10; ++i) cout<" "; cout<for(int i = 1;i <= k + 10; ++i) {
            for(int j = 1;j <= k + 10 - i; ++j) g[j] = g[j + 1] - g[j];
            for(int j = 1;j <= k + 10 - i; ++j) cout<" "; cout</*运行结果
3 3 3 3
1 10 46 146 371 812 1596 2892 4917 7942 12298 18382 26663
9 36 100 225 441 784 1296 2025 3025 4356 6084 8281
27 64 125 216 343 512 729 1000 1331 1728 2197
37 61 91 127 169 217 271 331 397 469
24 30 36 42 48 54 60 66 72
6 6 6 6 6 6 6 6
0 0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0
0 0 0 0
0 0 0
0 0
0
*/

对于f(n)也是一样的操作
然后我们就可以g(n)插值一次,f(n)插值一次就可以啦。

代码

/**************************************************************
    Problem: 3453
    User: 2014lvzelong
    Language: C++
    Result: Accepted
    Time:32 ms
    Memory:1300 kb
****************************************************************/

#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int N = 155, p = 1234567891;
long long read() {
    char ch = getchar(); long long x = 0, f = 1;
    while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
int T, k;
long long inv[N << 1], s1[N], s2[N], g[N], f[N], a, d, n, m;
void init(int n) {
    inv[0] = inv[1] = 1;
    for(int i = 2;i <= n; ++i) inv[i] = inv[p % i] * (p - p / i) % p;
    for(int i = 2;i <= n; ++i) inv[i] = inv[i] * inv[i - 1]  % p;
}
void up(long long &a, long long b) {(a += b) %= p;}
long long Lagrange(long long *a, int n, long long pos) {
    if(pos <= n) return a[pos];
    s1[0] = s2[n + 1] = 1;
    for(int i = 1;i <= n; ++i) s1[i] = s1[i - 1] * (pos - i) % p;
    for(int i = n; i; --i) s2[i] = s2[i + 1] * (pos - i) % p;
    long long ans = 0;
    for(int i = 1;i <= n; ++i) 
        up(ans, a[i] * s1[i - 1] % p * s2[i + 1] % p * inv[i - 1] % p * 
        inv[n - i] % p * ((n - i) & 1 ? -1 : 1));
    return ans;
}
long long mpower(long long x, long long y) {
    long long ans = 1;
    for(;y; y >>= 1, (x *= x) %= p)
        if(y & 1) (ans *= x) %= p;
    return ans;
}

int main() {
    T = read();
    init(300);
    while(T--) {
        k = read(); a = read(); n = read(); d = read();
        for(int i = 1;i <= k + 3; ++i) g[i] = mpower(i, k);
        for(int i = 2;i <= k + 3; ++i) up(g[i], g[i - 1]);
        for(int i = 2;i <= k + 3; ++i) up(g[i], g[i - 1]);
        f[0] = Lagrange(g, k + 3, a);
        for(int i = 1;i <= k + 5; ++i) 
            f[i] = Lagrange(g, k + 3, (i * d + a) % p), up(f[i], f[i - 1]);
        printf("%lld\n", (Lagrange(f, k + 5, n) + p) % p);
    }
    return 0;
}

你可能感兴趣的:(数学相关-插值法)