2019 ICPC 南昌邀请赛 B-Polynomial(拉格朗日插值法0~n)

55 篇文章 0 订阅
46 篇文章 0 订阅

暂时没有补题链接,等重现赛

题目

定义了 f ( x ) = a 0 + a 1 x 1 + a 2 x 2 + ⋯ + a n x n f_{(x)}=a_{0}+a_{1} x^{1}+a_{2} x^{2}+\cdots+a_{n} x^{n} f(x)=a0+a1x1+a2x2++anxn,数字可能会非常大,所以对9999991取模。对于一个多项式,XH不知道任意一个 a i a_i ai,但是他知道 f ( 0 ) , f ( 1 ) , f ( 2 ) ⋯ f ( n ) f_{(0)}, f_{(1)}, f_{(2)} \cdots f_{(n)} f(0),f(1),f(2)f(n) 的值,他想要计算 ∑ i = L R f i   m o d   9999991 \sum_{i=L}^{R} f_{i} \bmod 9999991 i=LRfimod9999991

分析

裸的拉格朗日插值,但是要变形一下。

拉格朗日总结模板链接

首先根据 n + 1 n+1 n+1 个点,可以用拉格朗日插值法插出一个 n n n 次的多项式,题目给出了 0 到 n, n + 1 n+1 n+1 个点,那么我们可以插出 f ( x ) f(x) f(x)

但是注意:题目让求的是 f ( x ) f(x) f(x) 的区间和,

那么很自然可以想到求出预处理求出前缀和,那么区间和就是 O ( 1 ) O(1) O(1)了。定义前缀和 S ( x ) = ∑ i = 0 x f ( i ) S(x)=\sum_{i=0}^{x} f(i) S(x)=i=0xf(i)

这里有个常识,n次多项式的前缀和是 n+1 次的多项式,也就是说 S ( x ) S(x) S(x) 要通过 n+2 个点来求出,然而题目只给出了n+1 个点。

少的那一个点可以先通过 n+1 个点将 f ( x ) f(x) f(x)求出,之后得到 f ( n + 1 ) f(n+1) f(n+1) ,之后就可以对 S ( x ) S(x) S(x) 插值了。

关于代码方面,插值的函数我重载成了一个,传入的参数不同功能也不一样,但是本题求 f ( x ) , S ( x ) f(x), S(x) f(x),S(x) 的函数是一样的。

赛后打的仅过了样例,

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 9999991;
const int N = 1e3 + 10;

ll t, n, m, l, r;
ll a[N], sum[N], pre[N], suf[N], fac[N];

ll qpow(ll x, ll y){
    ll ans = 1;
    while(y){
        if(y&1)
            ans = ans * x % mod;
        x = x * x % mod, y >>= 1;
    }
    return ans;
}


/*  cal 函数,用从 0 到 up,一共 up+1 个数,
    传入的 a 数组代表 yi
    插值出原来的 up+2 次方的多项式
    并返回 原来的多项式在 x 出的取值。 
*/

ll cal(ll x, ll *a, ll up)        //计算 a(n+1)
{   
    ll ans = 0;
    pre[0] = x, suf[up+1] = 1;
    for(int i = 1; i <= up; i++){
        pre[i] = pre[i-1] * (x-i) % mod;
    }
    for(int i = up; i >= 0; i--){
        suf[i] = suf[i+1] * (x-i) % mod;
    }
    for(int i = 0; i <= up; i++){
        ll f = fac[up - i] * fac[i] % mod;
        f = (up - i) & 1 ? -f : f;
        (ans += a[i] * f % mod * (i == 0 ? 1 : pre[i - 1]) % mod * suf[i + 1]) %= mod;
    }
    ans += ans < 0 ? mod : 0;
    return ans;
}

void init()
{
    fac[0] = 1;
    for (int i = 1; i < N; i++){
        fac[i] = fac[i - 1] * i % mod;
    }
    for(int i = 0; i < N; i++){
        fac[i] = qpow(fac[i], mod - 2);
    }
}

int main()
{
    init();
    scanf("%d", &t);
    while(t--){
        scanf("%d%d", &n, &m);
        for(int i = 0; i <= n; i++){
            scanf("%d", &a[i]);
        }
        a[n+1] = cal(n+1, a, n);      //插出 f(n+1)
        // d(a[n + 1]);
        sum[0] = a[0];
        for(int i = 1; i <= n+1; i++){
            sum[i] = sum[i - 1] + a[i] % mod;
        }
        while(m--){
            scanf("%d%d", &l, &r);
            ll cnt = cal(r, sum, n+1) - cal(l - 1, sum, n+1);
            cnt += cnt < 0 ? mod : 0;
            printf("%lld\n", cnt);
        }
    }
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值