专题·拉格朗日差值【including 拉格朗日差值,重心拉格朗日差值,洛谷·【模板】
初见安~敲标题的时候真心觉得自己像个复读机……【啪。
一、拉格朗日基本多项式
首先,我们知道n+1个点值可以确定一个n次的多项式。换言之,可以理解为:平面上n+1个点,我们就可以确定一个n次的函数保证贯穿着n+1个点。那么这个函数怎么求呢?这就是拉格朗日基本多项式:
很好理解其成立性:当n+1个点的其中一个点带入时,除了该点对应的那一项外,其余每一项都会因为累乘部分出现0而为0。
可能看起来有点难懂,那就随手举个例子吧:
这就没了。【??!
很明显,算对于每个点的l,需要的复杂度;而我们要求n+1个点的,所以复杂度为。
整个板子放个代码:洛谷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取值连续的情况,所以预处理阶乘、阶乘逆元后的这种做法处理单个值的复杂度只有。
三、重心拉格朗日差值
这个重心二字的含义——我也不知道【啪。
这个算法主要是应用于重过程的情况【目前还没遇到】,动作类似于:连续插入一些值,不断更新这个多项式。
因为没有连续的条件,乍一看我们每次插入都要花费的复杂度。其实也可以不用。就像我们之前化简公式的那样:
首先前面那一部分关于k的累乘我们可以算出;后面那个累乘,我们单独看:
假设,那么很明显:因为我们是不断插入每个值的,所以每个点都会有自己的t_i。也就是说,我们计算的过程可以分为三步:
1.对于前面的每个点的t_i,累乘部分乘上新加的这个点的贡献,复杂度;
2.对于新加入的这个点的t_i,计算其t_i的值,复杂度;
3.倒腾进多项式里,此时多项式形如:
很明显, 这也是一个的计算过程,并且这三个操作是并排的,所以总的复杂度也为。
所以每次插入一个点,修改这个多项式的复杂度就是。
再整一个例题吧:51nod P1258 序列求和
给出n、k,求。
n特别大,所以明显我们不能暴力。拉格朗日差值有个好处是什么呢, 就是一个k次的多项式,我们只需要知道某k+1个点的值,就可以求出其他所有点的值了。放到这个题上来,次数看似好像很不好找,但是我们知道:当k为1的时候,等差数列求和,次数是2。所以我们可以【盲猜】这是一个k+1次的多项式,需要我们处理k+2个点的值。
所以我们在复杂度的情况下处理出连续的前k+2个值后带入要求的点值拉格朗日差值即可。总复杂度。
这里放代码作参考:)
#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——
上一篇: Matlab差值计算