初見安~敲標題的時候真心覺得自己像個復讀機……【啪。
一、拉格朗日基本多項式
首先,我們知道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——