数列求值
题目链接:ybt金牌导航8-3-2
题目大意
给出一个不超过 m-1 次的函数,给出 x 为 0~m-1 时 y 的值,然后求 x 为 k 时 y 的值。
思路
这一题看到函数自然会想到拉格朗日插值法。
但是你会看到它的范围很大,(n^2) 是过不了的。
但是你会发现它有一个特殊的地方:给出的点是连续的,而且 (x_i=i)。
那你考虑能不能化简式子。
先把式子拿出来:
(f(k)=sumlimits_{i=1}^{n}(y_iprodlimits_{i
eq j}dfrac{k-x_j}{x_i-x_j}))
然后 (x_i=i):
(f(k)=sumlimits_{i=1}^{n}(y_iprodlimits_{i
eq j}dfrac{k-j}{i-j}))
那累乘的不等号条件很烦,我们就把它分开乘两个累乘:
(f(k)=sumlimits_{i=1}^{n}(y_iprodlimits_{1leq j<i}dfrac{k-j}{i-j}prodlimits_{i<jleq n}dfrac{k-j}{i-j}))
那这里我们就发现它分子的部分分别是可以用前缀积和后缀积解决。
然后再看分母,你会发现它是两个阶乘。
那你就预处理这三个东西,就可以用了。
在处理阶乘的时候,我们可以预先把它的逆元算出来。
然后不要一个一个都转逆元,会 T,你可以求出最大的那个的逆元,然后每次再乘回去,就可以得到较小的阶乘的逆元了。
还有。
不要以为我的题意写错了。
给出的就是从 (0sim m-1) 的,题目是有问题的,连样例都错了。
正确的样例输出的应该是 (9),而不是 (7)。
然后我这里因为是后面才发现,就把它改成了从 (0) 开始记录数组,运算什么的。
上面的公式的循环范围也要小改一下,这里就不再写一次了。
(如果不知道怎么改可以看代码)
代码
#include<cstdio>
#define ll long long
#define mo 998244353
using namespace std;
ll m, k, a[1000001];
ll y[1000001];
ll q[1000001], b[1000001];
ll jc[1000001], ans, now;
ll ksm(ll x, ll y) {//快速幂求逆元
if (x < 0) x = (x % mo + mo) % mo;
ll re = 1;
while (y) {
if (y & 1) re = (re * x) % mo;
x = (x * x) % mo;
y >>= 1;
}
return re;
}
int main() {
scanf("%lld %lld", &m, &k);
m--;
q[0] = k - 0;
scanf("%d", &y[0]);
for (ll i = 1; i <= m; i++) {
scanf("%lld", &y[i]);
q[i] = q[i - 1] * ((k - i + mo) % mo) % mo;//处理分子的前缀积
}
b[m + 1] = 1ll;
for (ll i = m; i >= 0; i--) {//处理分子的后缀积
b[i] = b[i + 1] * ((k - i + mo) % mo) % mo;
}
jc[0] = 1ll;//求分母的阶乘
for (ll i = 1; i <= m; i++)
jc[i] = jc[i - 1] * i % mo;
jc[m] = ksm(jc[m], mo - 2);//现在就把分母的阶乘的逆元弄好
for (ll i = m - 1; i >= 0; i--)
jc[i] = (jc[i + 1] * (i + 1)) % mo;//不要每个都求逆元,会T
for (ll i = 0; i <= m; i++) {
now = y[i];//按照公式乘
now = (now * (((i - 1 < 0) ? 1 : q[i - 1]) * b[i + 1] % mo)) % mo;
now = (now * (jc[i] * jc[m - i] % mo)) % mo;
now = (now * ((m - i) & 1 ? -1 : 1)) % mo;
if (now < 0) now += mo;
ans = (ans + now) % mo;
}
printf("%lld", ans);
return 0;
}