專題·拉格朗日差值【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——

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章