【数学】拉格朗日插值

【数学】拉格朗日插值

题目描述

由小学知识可知 n 个点 (xi,yi) 可以唯一地确定一个多项式 y=f(x)

现在,给定这 n 个点,请你确定这个多项式,并求出 f(k)mod998244353 的值。

1n2×1031xi,yi,k<998244353xi 两两不同。

算法描述

考虑构造一个多项式,满足xi,f(xi)=yi。考虑用一种手法,使若干项加起来,当x=xi时,其他项全部是0,只有一项是yi。即:

f(x)=i=1nyigi(xi)

其中

gi(x)={0   x=xj(ji)1   x=xi

对于gi(x)只有在xi这个值时才等于1这个性质,我们考虑用乘法来实现,即:

ij(xxj)

这样就能做到当x=xi时它不为0,x=xj(ji)时为0(其他值不做要求)

那么怎样才能让x=xi时等于1呢?观察到x=xi时这个值等于

ij(xixj)

所以除掉一个相同的值就好了。所以

gi(x)=ij(xxj)(xixj)

f(x)=i=1nyiij(xxj)(xixj)

时间复杂度O(n2)

对于这道题n2×103的情况(也是一般情况),直接做即可。

Code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MOD = 998244353;
inline ll ksm(ll base,ll pts)
{
    ll ret = 1;
    for(;pts > 0;pts >>= 1,base = base * base % MOD)
        if(pts & 1)
            ret = ret * base % MOD;
    return ret; 
}
ll n,k,x[2005],y[2005];
inline ll g(ll i,ll X)
{
    ll ret = 1;
    for(int j = 1;j <= n;j++)
    {
        if(i == j) continue;
        ret = ret * (X - x[j] + MOD) % MOD;
    }
    for(int j = 1;j <= n;j++)
    {
        if(i == j) continue;
        ret = ret * ksm((x[i] - x[j] + MOD) % MOD,MOD - 2) % MOD;
    }
    return ret;
}
int main()
{
    cin>>n>>k;
    for(int i = 1;i <= n;i++)
        cin>>x[i]>>y[i];
    ll ans = 0;
    for(int i = 1;i <= n;i++)
        ans += y[i] * g(i,k) % MOD,ans %= MOD;
    cout<<ans;
    return 0;
 } 

但是对于很多更加灵活的题目,时间复杂度要求更高,并且点值是自己选的,这个时候就可以利用点值自选的性质,降低复杂度至O(n)

例题:The Sum of the k-th Powers - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目描述

题目描述:求 (i=1nik)mod(109+7)

数据范围:1n109,0k106

算法描述

这个题又叫“自然数幂前缀和”,有广泛的应用,看到自然数的k次方求前缀和,猜想它是一个k+1次多项式(一般几项加起来不行!),下面进行证明。

我们使用归纳法,假设

fn,k=i=1nik

并假设fn,1fn,k2已经都满足这个规律。

转化一下式子:

(n+1)knk=i=0k(ki)ni      nk=i=0k1(ki)ni

(n+1)k1k=(n+1)knk+nk(n1)k+...+2k1k

=j=1ni=0k1(ki)ji

=i=0k1(ki)j=1nji

注意到j=1nji其实就是fn,i,所以

(n+1)k1=i=0k1(ki)fn,i

=i=0k2(ki)fn,i   +   kfn,k1

(n+1)k1i=0k2(ki)fn,i=fn,k1

由归纳我们可以知道,fn,k2不超过k1次,所以左边等式最高次是k次,右边也是。

由于fn,1是一个二次多项式(高斯公式求和从1加到n),这样,我们就证得了fn,k1是一个k次多项式。

所以i=1nik是一个k+1次多项式。

知道了这点,由于n很大,k较小,我们求出k+2个连续点值然后带进去拉格朗日插值求出答案即可。

然而我们怎样在O(k)的时间内完成插值呢?

考虑到点值连续,它们互相之间的差就是连续的,这里假设它们是1k+2,代入式子,有:

f(x)=i=1k+2yiij(xxj)(xixj)

=i=1k+2yiij(xj)(ij)

=i=1k+2yiij(xj)(i1)!(k+2i)!(1)k+2i

=i=1k+2yij=1k+2(xj)(i1)!(k+2i)!(1)k+2i(xi)

k+2以内的阶乘及其逆元和j=1k+2(xj)都是可以通过预处理O(k)处理出来,在这里直接O(1)计算后面分式中的值即可。

时间复杂度O(k)

Code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5;
ll n,m,a[N],k,x[N],y[N],s[N],Pd = 1;
const int MOD = 1e9 + 7;
inline ll ksm(ll base,ll pts)
{
	ll ret = 1;
	for(;pts > 0;pts >>= 1,base = base * base % MOD)
		if(pts & 1)
			ret = ret * base % MOD;
	return ret;
}
inline ll f(ll X)
{
	ll ret = 0;
	for(int i = 1;i <= k + 2;i++)
	{
		ll add = y[i];
		add = add * Pd % MOD * ksm(X - x[i],MOD - 2) % MOD;
		add = add * ((((k + 2 - i) % 2 == 0) ? 1 : -1) + MOD) % MOD * ksm(s[i - 1],MOD - 2) % MOD * ksm(s[k + 2 - i],MOD - 2) % MOD;
		ret = (ret + add) % MOD;
	}
	return ret;
}
int main()
{
	cin>>n>>k;
	s[0] = 1;
	for(int i = 1;i <= k + 2;i++) s[i] = s[i - 1] * i % MOD;
	for(int i = 1;i <= k + 2;i++)
	{
		x[i] = i;
		y[i] = ksm(i,k);
		y[i] = (y[i] + y[i - 1]) % MOD;
		Pd = Pd * ((n - x[i] + MOD) % MOD) % MOD;
	}
	if(n <= k + 2) cout<<y[n];
	else cout<<f(n);
	return 0;
}
posted @   The_Last_Candy  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示