已知点值求多项式
拉格朗日插值 as we all know , $n$ 个点可以唯一确定一个 $n - 1$ 次多项式,那么现在,给你 $n$ 个点,你该如何确定这个多项式呢?当然,你可以 gauss 消元解方程,但过于麻烦,且复杂度较高,这里就有一个新方法——拉格朗日插值法
式子 给定 $n$ 个点 $(x_1, y_1), (x_2, y_2), …, (x_n, y_n)$ 它们确定的 $n - 1$ 次多项式为: $$ f(x) = \sum_{i = 1}^n y_i \prod_{j \ne i}\frac{(x - x_j)}{(x_i - x_j)} $$ 显然,当 $x = x_i$ 的时候, $y_i$ 后面的系数为 $1$ ,而 $\forall j \ne i, y_j$ 后面的系数都为 $0$ (因为有 $(x - x_i)$ 这一项),那么 $f(x)$ 就过点 $(x_i, y_i)$ ,这就是我们要求的多项式
一般拉插 【模板】拉格朗日插值
就是上面的式子,预处理是 $O(n^2)$ (有些题要预处理逆元但一般瓶颈不在求逆元上),每次带入求点是 $O(n^2)$ ,想得到原多项式也是 $O(n^2)$ 的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <cstdio> #include <algorithm> #define fi first #define se second using LL = long long ;const int N = 2e3 + 100 , P = 998244353 ;int n, b, la[N];std::pair<int , int > a[N]; void adj (int &x) { x += (x >> 31 ) & P; }int qpow (int x, int y = P - 2 ) { int res = 1 ; for (; y; y >>= 1 , x = LL (x) * x % P) if (y & 1 ) res = LL (res) * x % P; return res; } void init () { for (int i = 1 , t; i <= n; ++i) { la[i] = 1 ; for (int j = 1 ; j <= n; ++j) if (j != i) { adj (t = a[i].fi - a[j].fi); la[i] = LL (t) * la[i] % P; } la[i] = qpow (la[i]); } } int lag (int x) { int res = 0 ; for (int i = 1 , t, tt; i <= n; ++i) { tt = la[i]; for (int j = 1 ; j <= n; ++j) if (j != i) { adj (t = x - a[j].fi); tt = LL (t) * tt % P; } adj (res += LL (tt) * a[i].se % P - P); } return res; } int main () { scanf ("%d %d" , &n, &b); for (int i = 1 ; i <= n; ++i) scanf ("%d %d" , &a[i].fi, &a[i].se); init (); printf ("%d\n" , lag (b)); return 0 ; }
线性拉插 在很多题目中,取的 $n$ 个点是连续的(或者让我们自己取,那就取连续的),此时我们一般可以做到询问线性
考虑我们求的点是 $(1, f(1)), (2, f(2)), …, (n, f(n))$ ,那么带入式子就是: $$ \begin{aligned} f(x) = & \sum_{i = 1}^n f(i) \prod_{j \ne i}\frac{(x - j)}{(i - j)} \\ = & \sum_{i = 1}^n f(i) * (-1)^{n - i} * \frac{(x - 1)(x - 2)…(x - (i - 1))(x - (i + 1))(x - (i + 2))…(x - n)}{(i - 1)!(n - i)!} \\ \end{aligned} $$ 预处理 $f(i)$ 和分母,复杂度的关键在 $f(i)$ 的求取和逆元的处理,每次查询的时候维护前、
后缀积,可以做到线性查询
值得一提的是,如果 $x - x_i$ 的逆元好求或者求它不在瓶颈上,查询时可以先算出 $\prod (x - x_i)$ ,然后每项用逆元除去
The Sum of the k-th Powers
经典的求自然数幂和,一个结论是: $\sum_{i = 1}^n i^k$ 是一个关于 $n$ 的 $k + 1$ 次多项式,复杂度瓶颈在预处理求 $f(i)$ 的地方,总复杂度 $O(k \log k)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <cstdio> #include <algorithm> #define fi first #define se second using LL = long long ;const int N = 1e6 + 100 , P = 1e9 + 7 ;int n, k;std::pair<int , int > a[N]; int la[N], la2[N];int ifac[N];void adj (int &x) { x += (x >> 31 ) & P; }int qpow (int x, int y = P - 2 ) { int res = 1 ; for (; y; y >>= 1 , x = LL (x) * x % P) if (y & 1 ) res = LL (res) * x % P; return res; } void init (int n) { int fac = 1 , fi = 0 ; ifac[0 ] = ifac[1 ] = 1 ; for (int i = 2 ; i <= n; ++i) fac = LL (fac) * i % P; ifac[n] = qpow (fac); for (int i = n - 1 ; i > 1 ; --i) ifac[i] = LL (i + 1 ) * ifac[i + 1 ] % P; for (int i = 1 ; i <= n; ++i) { adj (fi += qpow (i, k) - P); la[i] = LL (fi) * ifac[i - 1 ] % P * ifac[n - i] % P; if ((n - i) & 1 ) la[i] = P - la[i]; } } int lag (int x, int n) { int res = 0 ; for (int i = 1 , t = 1 ; i <= n; ++i) { la2[i] = t; adj (t = LL (x - i) * t % P); } for (int i = n, t = 1 ; i; --i) { la2[i] = LL (t) * la2[i] % P; adj (t = LL (x - i) * t % P); } for (int i = 1 ; i <= n; ++i) adj (res += LL (la2[i]) * la[i] % P - P); return res; } int main () { scanf ("%d %d" , &n, &k); init (k + 2 ); printf ("%d\n" , lag (n, k + 2 )); return 0 ; }
重心拉插 设 $g = \prod (x - x_i), w_i = \frac{y_i}{\prod_{j \ne i} (x_i - x_j)}$ ,则式子为: $$ \begin{aligned} f(x) = g \sum_{i = 1}^n \frac{w_i}{x - x_i} \end{aligned} $$ 预处理是 $O(n^2)$ 不变,每次计算的复杂度变为 $O(n * t)$ ,其中 $t$ 是计算逆元的复杂度,但这个式子最好的地方在于,可以用 $O(n * t)$ 的时间插入一个新的点,只要计算出 $w_i$ 即可
注意处理 $x$ 相同的情况
拉格朗日插值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include <cstdio> #include <vector> #define eb emplace_back using LL = long long ;const int N = 3000 + 100 , P = 998244353 ;int n;std::vector<int > xn, yn, w; int qpow (int x, int y = P - 2 ) { int res = 1 ; for (; y; y >>= 1 , x = LL (x) * x % P) if (y & 1 ) res = LL (res) * x % P; return res; } void adj (int &x) { x += (x >> 31 ) & P; }void ins (int x, int y) { yn.eb (y); int t; for (int i = xn.size () - 1 ; ~i; --i) { adj (t = x - xn[i]); t = qpow (t); y = LL (y) * t % P; w[i] = LL (P - t) * w[i] % P; } xn.eb (x), w.eb (y); } int ask (int x) { int res = 0 , t, g = 1 ; for (int i = xn.size () - 1 ; ~i; --i) if (xn[i] == x) return yn[i]; else { adj (t = x - xn[i]); g = LL (t) * g % P; t = LL (w[i]) * qpow (t) % P; adj (res += t - P); } return LL (g) * res % P; } int main () { scanf ("%d" , &n); for (int op, x, y; n--; ) { scanf ("%d %d" , &op, &x); if (op == 1 ) { scanf ("%d" , &y); ins (x, y); } else printf ("%d\n" , ask (x)); } return 0 ; }
后话 其实还有一种 拉插 + 分治 ntt + 多项式多点求值 的 $O(n \log^2 n)$ 快速插值方法,后面有时间补充吧