专题·拉格朗日差值【including 拉格朗日差值,重心拉格朗日差值,洛谷·【模板】

初见安~敲标题的时候真心觉得自己像个复读机……【啪。

一、拉格朗日基本多项式

首先,我们知道n+1个点值可以确定一个n次的多项式。换言之,可以理解为:平面上n+1个点,我们就可以确定一个n次的函数保证贯穿着n+1个点。那么这个函数怎么求呢?这就是拉格朗日基本多项式:

很好理解其成立性:当n+1个点的其中一个点带入时,除了该点对应的那一项外,其余每一项都会因为累乘部分出现0而为0。

可能看起来有点难懂,那就随手举个例子吧:

这就没了。【??!

很明显,算对于每个点的l,需要O(n)的复杂度;而我们要求n+1个点的,所以复杂度为O(n^2)

整个板子放个代码:洛谷P4781 【模板】拉格朗日差值

题解

这就很模板了,直接按照上述方法求出多项式,并且过程中把要求的变量带进去算出来即可。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxn 2005
using namespace std;
typedef long long ll;
const int mod = 998244353;
int read() {
    int x = 0, f = 1, ch = getchar();
    while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
    while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
    return x * f;
}
 
int n, k, x[maxn], y[maxn];
ll pw(ll a, ll b) {ll res = 1; while(b) {if(b & 1) res = res * a % mod; a = a * a % mod; b >>= 1;} return res;}
ll ans = 0;
signed main() {
    n = read(), k = read();
    for(int i = 1; i <= n; i++) x[i] = read(), y[i] = read();
    for(int i = 1; i <= n; i++) {
        ll tmp = 1;
        for(int j = 1; j <= n; j++) if(j != i) tmp = tmp * (x[i] - x[j] + mod) % mod;
        tmp = pw(tmp, mod - 2);//tmp是下面那一部分,所以要求逆元
        for(int j = 1; j <= n; j++) if(j != i) tmp = tmp * (k - x[j] + mod) % mod;
        ans = (ans + tmp * y[i] % mod) % mod;//累加答案
    }
    printf("%lld\n", ans);
    return 0;
}

二、当x连续时的优化

这个算法明显是有某种优化空间的,因为那一坨累乘当x连续时,可以直接用阶乘之类的来表示。我们来观察一下:

可以发现:当我们要求单个值的时候,上半部分可以说是几乎没动,也就是说我们可以提出来:【当然也可以放在里面,同样用阶乘来求得】

再来看里面还剩的那个连乘。因为x的取值是连续的,所以这一部分连乘分解成正负两部分的话看起来就是两个阶乘。所以我们预处理阶乘,这一部分也就迎刃而解了:【注意符号】【这种写法的话在好些题目的公式化简上比较有用

当然,也可以不把那一部分提前,而是处理其前缀积和后缀积,处理后公式形如:【文末例题是用这种方法处理的

拉格朗日处理的问题大多都是x取值连续的情况,所以预处理阶乘、阶乘逆元后的这种做法处理单个值的复杂度只有O(n)

三、重心拉格朗日差值

这个重心二字的含义——我也不知道【啪。

这个算法主要是应用于重过程的情况【目前还没遇到】,动作类似于:连续插入一些值,不断更新这个多项式。

因为没有连续的条件,乍一看我们每次插入都要花费O(n^2)的复杂度。其实也可以不用。就像我们之前化简公式的那样:

首先前面那一部分关于k的累乘我们可以O(n)算出;后面那个累乘,我们单独看:

假设,那么很明显:因为我们是不断插入每个值的,所以每个点都会有自己的t_i。也就是说,我们计算的过程可以分为三步:

1.对于前面的每个点的t_i,累乘部分乘上新加的这个点的贡献,复杂度O(n)

2.对于新加入的这个点的t_i,计算其t_i的值,复杂度O(n)

3.倒腾进多项式里,此时多项式形如:

很明显, 这也是一个O(n)的计算过程,并且这三个操作是并排的,所以总的复杂度也为O(n)

所以每次插入一个点,修改这个多项式的复杂度就是O(n)

再整一个例题吧:51nod P1258 序列求和

给出n、k,求sum=\sum_{i=1}^ni^k

n特别大,所以明显我们不能暴力。拉格朗日差值有个好处是什么呢, 就是一个k次的多项式,我们只需要知道某k+1个点的值,就可以求出其他所有点的值了。放到这个题上来,次数看似好像很不好找,但是我们知道:当k为1的时候,等差数列求和,次数是2。所以我们可以【盲猜】这是一个k+1次的多项式,需要我们处理k+2个点的值。

所以我们在复杂度O(k)的情况下处理出连续的前k+2个值后带入要求的点值拉格朗日差值即可。总复杂度O(k)

这里放代码作参考:)

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxn 100005
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7, mx = 100000;
ll read() {
	ll x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

ll T, n, k, inv[maxn], y[maxn];
ll pw(ll a, ll b) {ll res = 1; while(b) {if(b & 1) res = res * a % mod; a = a * a % mod, b >>= 1;} return res;}

ll pre[maxn], suf[maxn];
ll solve() {
	if(n <= k + 2) return y[n];
	n %= mod;//这里一定要先取模
	pre[0] = suf[k + 3] = 1;//前缀积和后缀积
	for(int i = 1; i <= k + 2; i++) pre[i] = pre[i - 1] * (n - i) % mod;
	for(int i = k + 2; i > 0; i--) suf[i] = suf[i + 1] * (n - i) % mod;
	
	ll ans = 0;
	for(int i = 1; i <= k + 2; i++) //形如公式,记得处理符号(判断n-i的奇偶
		ans = (ans + y[i] * pre[i - 1] % mod * suf[i + 1] % mod * inv[i - 1] % mod * inv[k + 2 - i] % mod * ((k + 2 - i) & 1? -1 : 1) % mod + mod) % mod;
	return ans;
}

signed main() {
	T = read();
	inv[0] = 1;
	for(int i = 1; i <= mx; i++) inv[i] = inv[i - 1] * i % mod;
	inv[mx] = pw(inv[mx], mod - 2);
	for(int i = mx - 1; i > 0; i--) inv[i] = inv[i + 1] * (i + 1) % mod;
	while(T--) {
		n = read(), k = read();//预处理k+2个值
		for(int i = 1; i <= k + 2; i++) y[i] = (y[i - 1] + pw(i, k)) % mod;
		printf("%lld\n", solve());
	}
	return 0;
}

又是一个多钟头……哎。

迎评:)
——End——

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值