【算法讲14:拉格朗日插值】
- 前言
- 引入
- ⌈ \lceil ⌈拉格朗日插值 ⌋ \rfloor ⌋
- 代码
- ⌈ \lceil ⌈拉格朗日插值 ⌋ \rfloor ⌋差分法运用
- 代码
前言
-
拉格朗日插值可以出的很难,对于一个 n n n 阶多项式,可以优化成 O ( n log n ) O(n\log n) O(nlogn)
但是需要 F F T FFT FFT 等知识点,博主比较菜就只能搞搞入门的了 (哭)
-
基础的高斯消元法可以 O ( n 3 ) O(n^3) O(n3) 求解 n n n 阶多项式,但是貌似不够用呀
这时就出现了 O ( n 2 ) O(n^2) O(n2) 的拉格朗日插值!
对于能求出 f ( i ) , ∀ i ∈ [ 1 , n ] f(i),\forall i\in[1,n] f(i),∀i∈[1,n],我们用 差分法 还可以化简到 O ( n ) O(n) O(n)
引入
-
【题目描述】【模板】拉格朗日插值 | 洛谷 P4781
有 n n n 个不同点,给定他们的坐标 ( x i , y i ) (x_i,y_i) (xi,yi)
由于他们的横坐标都不相同,因此他们可以唯一确定一个 n − 1 n-1 n−1 阶的多项式 P ( x ) P(x) P(x)
给定 k k k ,你的任务是求出 P ( k ) P(k) P(k) 取模 998244353 998244353 998244353
-
【数据范围】
1 ≤ n ≤ 2 × 1 0 3 1\le n\le 2\times10^3 1≤n≤2×103
1 ≤ x i , y i , k < 998244353 1\le x_i,y_i,k<998244353 1≤xi,yi,k<998244353
⌈ \lceil ⌈拉格朗日插值 ⌋ \rfloor ⌋
-
我们知道多项式可以表示为 P ( x ) = ∑ a i x i P(x)=\sum a_ix^i P(x)=∑aixi。
很神奇的,我们想构造出一个函数来表示这个 P ( x ) P(x) P(x)
-
我们设一个函数:
l i ( x ) = ∏ j = 0 , j ≠ i n x − x j x i − x j l_i(x)=\prod_{j=0,j\ne i}^n\cfrac{x-x_j}{x_i-x_j} li(x)=j=0,j=i∏nxi−xjx−xj
这个函数有什么性质呢?有一下两个性质:
(1) ∀ i ∈ [ 0 , n ] , l i ( x i ) = 1 \forall i\in[0,n],l_i(x_i)=1 ∀i∈[0,n],li(xi)=1
(2) ∀ i , j ∈ [ 0 , n ] , i ≠ j , l i ( x j ) = 0 \forall i,j\in[0,n],i\ne j,l_i(x_j)=0 ∀i,j∈[0,n],i=j,li(xj)=0
接下来,我们就能直接构造出 P ( x ) P(x) P(x)了:
P ( x ) = ∑ i = 0 n y i l i ( x ) P(x)=\sum_{i=0}^n y_il_i(x) P(x)=i=0∑nyili(x)
为啥这个多项式保证是正确的呢?因为我们有:
∀ i ∈ [ 0 , n ] , P ( x i ) = y i \forall i\in[0,n],P(x_i)=y_i ∀i∈[0,n],P(xi)=yi,保证成立。
代码
- 时间复杂度: O ( n 2 ) O(n^2) O(n2)
const int MAX = 2e3+50;
const ll MOD = 998244353;
ll qpow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a%MOD;a=a*a%MOD;n>>=1;}return res;}
ll inv(ll a){/* */return qpow(a,MOD-2);}
ll X[MAX],Y[MAX];
int main()
{
int n;ll k;
cin >> n >> k;
for(int i = 0;i < n;++i){
cin >> X[i] >> Y[i];
}
ll res = 0;
for(int i = 0;i < n;++i){
ll num = Y[i];
ll den = 1;
for(int j = 0;j < n;++j){
if(i != j){
num = num * (k - X[j]) % MOD;
den = den * (X[i] - X[j]) % MOD;
}
}
num = (num + MOD) % MOD;
den = (den + MOD) % MOD;
res = (res + num % MOD * inv(den) % MOD) % MOD;
}
cout << res;
return 0;
}
⌈ \lceil ⌈拉格朗日插值 ⌋ \rfloor ⌋差分法运用
-
【题目描述】The Sum of the k-th Powers | CF 622F
给定 n 、 k n、k n、k,求:
∑ i = 1 n i k \sum_{i=1}^ni^k i=1∑nik
-
【数据范围】
1 ≤ n ≤ 1 0 9 1\le n\le 10^9 1≤n≤109
1 ≤ k ≤ 1 0 6 1\le k\le 10^6 1≤k≤106
-
【思路】
这下阶为 1 e 6 1e6 1e6了, O ( k 2 ) O(k^2) O(k2) 已经不适用了。
我们如果算出前面一些 x i x_i xi 和 y i y_i yi,想快速算 l i ( n ) l_i(n) li(n),怎么办呢?
如果我们令 x i = i x_i=i xi=i,那么式子会变成下面的样子:
l i ( n ) = ∏ j = 1 , j ≠ i k n − j i − j l_i(n)=\prod_{j=1,j\ne i}^k\cfrac{n-j}{i-j} li(n)=j=1,j=i∏ki−jn−j
(1)对于分子,我们只要预处理出一个前缀积和后缀积,即可快速算。
令 p r e ( i ) = ∏ j = 1 i n − j pre(i)=\prod_{j=1}^i n-j pre(i)=∏j=1in−j, s u f ( i ) = ∏ j = i k n − j suf(i)=\prod_{j=i}^k n-j suf(i)=∏j=ikn−j
那么分子就是 p r e ( i − 1 ) × s u f ( i + 1 ) pre(i-1)\times suf(i+1) pre(i−1)×suf(i+1)
(2)对于分母,容易想到就是一个 断开的阶乘,就是 f a c [ i − 1 ] × f a c [ k − i ] fac[i-1]\times fac[k-i] fac[i−1]×fac[k−i]
不过要注意一下,因为当 j > i j>i j>i 时,后面每一项都为负数。若 k − i k-i k−i是一个奇数,那么后面会多一个负号。
P ( n ) = ∑ i = 1 k y i × p r e ( i − 1 ) × s u f ( i + 1 ) f a c [ i ] × f a c [ k − i ] × ( − 1 ) k − i P(n)=\sum_{i=1}^ky_i\times\cfrac{pre(i-1)\times suf(i+1)}{fac[i]\times fac[k-i]}\times(-1)^{k-i} P(n)=i=1∑kyi×fac[i]×fac[k−i]pre(i−1)×suf(i+1)×(−1)k−i
这下,时间复杂度变为线性的了!
代码
-
342 M s / 2000 M s 342Ms/2000Ms 342Ms/2000Ms
时间复杂度: O ( k ) O(k) O(k)
/*
_ __ __ _ _
| | \ \ / / | | (_)
| |__ _ _ \ V /__ _ _ __ | | ___ _
| '_ \| | | | \ // _` | '_ \| | / _ \ |
| |_) | |_| | | | (_| | | | | |___| __/ |
|_.__/ \__, | \_/\__,_|_| |_\_____/\___|_|
__/ |
|___/
*/
const int MAX = 1e6+50;
const ll MOD = 1e9+7;
ll qpow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a%MOD;a=a*a%MOD;n>>=1;}return res;}
ll inv(ll a){/* */return qpow(a,MOD-2);}
ll Y[MAX];
ll fac[MAX];
ll ivfac[MAX];
ll pre[MAX],suf[MAX];
void init(ll n,ll k){
fac[0] = 1;
pre[0] = 1;
for(int i = 1;i <= k;++i){
fac[i] = (fac[i-1] * i) % MOD;
pre[i] = pre[i - 1] * (n - i) % MOD;
}
ivfac[k] = inv(fac[k]);
suf[k+1] = 1; /// 注意一下,不存在的话设为 1
suf[k] = n - k;
for(int i = k - 1;i >= 0;--i){
ivfac[i] = ivfac[i + 1] * (i + 1) % MOD;
suf[i] = suf[i + 1] * (n - i) % MOD;
}
}
int main()
{
ll n,k;
cin >> n >> k;
k += 2; /// 因为是 k+2 阶的多项式
init(n,k);
ll sum = 0;
for(int i = 1;i <= k;++i){
sum = (sum + qpow(i,k - 2)) % MOD;
Y[i] = sum;
if(n == i){
cout << sum;
return 0;
}
}
ll res = 0;
for(int i = 1;i <= k;++i){
ll num = Y[i] * pre[i - 1] % MOD * suf[i + 1] % MOD;
ll den = ivfac[i - 1] * ivfac[k - i] % MOD;
if((k - i) % 2 == 1)den = -den;
res = (res + num * den) % MOD;
}
res = (res + MOD) % MOD;
cout << res;
return 0;
}