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

2019 ICPC 南昌邀请赛-Polynomial 拉格朗日插值法


传送门: https://nanti.jisuanke.com/t/40254

题意

f ( x ) = a 0 + a 1 x + . . . + a n x n , 没 有 给 出 a 0 , a 1 . . . a n , 只 知 道 f ( 0 ) , f ( 1 ) . . . f ( n ) , 求 f(x)=a_0+a_1x+...+a_nx^n,没有给出a_0,a_1...a_n,只知道f(0),f(1)...f(n),求 f(x)=a0+a1x+...+anxna0,a1...anf(0),f(1)...f(n)
∑ i = L R f ( i )      m o d      9999991 \sum_{i=L}^Rf(i)\;\;mod\;\;9999991 i=LRf(i)mod9999991

思路

看 到 这 个 形 式 , 就 知 道 用 拉 格 朗 日 插 值 法 了 , 即 看到这个形式,就知道用拉格朗日插值法了,即

f [ k ] = ∑ i = 0 n y i ∏ i ≠ j k − x [ j ] x [ i ] − x [ j ] f[k]=\sum_{i=0}^ny_i\prod_{i\ne j}\frac{k-x[j]}{x[i]-x[j]} f[k]=i=0nyii=jx[i]x[j]kx[j]

但 是 题 目 中 给 出 的 点 都 是 连 续 的 , ( 0 , f ( 0 ) ) 、 ( 1 , f ( 1 ) ) . . . ( n , f ( n ) ) , 也 就 是 x 连 续 , 所 以 插 值 式 变 为 但是题目中给出的点都是连续的,(0,f(0))、(1,f(1))...(n,f(n)),也就是x连续,所以插值式变为 (0,f(0))(1,f(1))...(n,f(n))x
f [ k ] = ∑ i = 0 n y i ∏ i ≠ j k − j i − j f[k]=\sum_{i=0}^ny_i\prod_{i\ne j}\frac{k-j}{i-j} f[k]=i=0nyii=jijkj

所 以 一 般 的 拉 格 朗 日 插 值 的 复 杂 度 为 O ( n 2 ) , 而 当 x 连 续 的 时 候 , 复 杂 度 一 度 降 为 O ( n ) 。 所以一般的拉格朗日插值的复杂度为O(n^2),而当x连续的时候,复杂度一度降为O(n)。 O(n2)xO(n)

将 上 式 展 开 : 将上式展开:

f [ k ] = ∑ i = 0 n y i k ( k − 1 ) ( k − 2 ) . . . ( k − n ) [ i ( i − 1 ) ( i − 2 ) . . . 1 ] ∗ [ ( − 1 ) ∗ ( − 2 ) ∗ . . . ∗ ( i − n ) ] f[k]=\sum_{i=0}^ny_i\frac{k(k-1)(k-2)...(k-n)}{[i(i-1)(i-2)...1]*[(-1)*(-2)*...*(i-n)]} f[k]=i=0nyi[i(i1)(i2)...1][(1)(2)...(in)]k(k1)(k2)...(kn)

f [ k ] = ∑ i = 0 n y i ∏ j = 0 i − 1 ( k − j ) ∏ j = i + 1 n ( k − j ) i ! ∗ ( i − n ) ! f[k]=\sum_{i=0}^ny_i\frac{\prod_{j=0}^{i-1}(k-j)\prod_{j=i+1}^n(k-j)}{i!*(i-n)!} f[k]=i=0nyii!(in)!j=0i1(kj)j=i+1n(kj)

f [ k ] = ∑ i = 0 n y i p r e [ i − 1 ] ∗ s u f [ i + 1 ] f a c [ i ] ∗ f a c [ n − i ] [ ( n − i ) & 1 ? − 1 : 1 ] f[k]=\sum_{i=0}^ny_i\frac{pre[i-1]*suf[i+1]}{fac[i]*fac[n-i]}[(n-i) \&1 ?-1:1] f[k]=i=0nyifac[i]fac[ni]pre[i1]suf[i+1][(ni)&1?1:1]

对 于 分 母 , 可 以 预 处 理 阶 乘 和 阶 乘 逆 元 。 对于分母,可以预处理阶乘和阶乘逆元。
对 于 分 子 , 做 一 个 前 缀 积 和 一 个 后 缀 积 。 对于分子,做一个前缀积和一个后缀积。

所 以 首 先 , 先 插 出 一 个 f ( n + 1 ) , 在 对 f 进 行 一 个 前 缀 和 s u m 数 组 , 然 后 直 接 对 s u m 数 组 进 行 插 值 , 计 算 s u m [ R ] − s u m [ l − 1 ] 即 可 。 所以首先,先插出一个f(n+1),在对f进行一个前缀和sum数组,然后直接对sum数组进行插值,计算sum[R]-sum[l-1]即可。 f(n+1)fsumsumsum[R]sum[l1]

注 意 : 为 什 么 要 插 出 一 个 f ( n + 1 ) , 是 因 为 要 先 对 s u m 数 组 有 插 值 这 个 影 响 , 而 s u m 又 是 f 的 前 缀 和 , 所 以 要 先 插 出 一 个 f ( n + 1 ) 来 , 所 以 也 可 以 插 f ( n + 2 ) 、 f ( n + 3 ) 都 可 以 , 只 要 有 这 个 影 响 就 行 。 注意:为什么要插出一个f(n+1),是因为要先对sum数组有插值这个影响,而sum又是f的前缀和,所以要先插出一个f(n+1)来,所以也可以插f(n+2)、f(n+3)都可以,只要有这个影响就行。 f(n+1)sumsumff(n+1)f(n+2)f(n+3)

Code

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef long double ld;
typedef pair<int, int> pdd;

#define INF 0x3f3f3f3f
#define lowbit(x) x & (-x)
#define mem(a, b) memset(a , b , sizeof(a))
#define FOR(i, x, n) for(int i = x;i <= n; i++)

// const ll mod = 998244353;
// const ll mod = 1e9 + 7;
// const double eps = 1e-6;
// const double PI = acos(-1);
// const double R = 0.57721566490153286060651209;

const int N = 1005;
const int MAXN = 1e7 + 10;
const ll mod = 9999991;
ll F[N];
ll pre[N], suf[N];
ll fac[N], invf[N];
ll sum[MAXN];

ll quick_pow(ll a, ll b) {
    ll ans = 1;
    while(b) {
        if(b & 1) ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}

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

ll Lagrange(ll *f, int k, int n) {
    if(k <= n) return f[k];
    pre[0] = suf[n] = 1;
    for(int i = 1;i <= n; i++) pre[i] = pre[i - 1] * (k - i + 1) % mod;
    for(int i = n;i >= 1; i--) suf[i - 1] = suf[i] * (k - i) % mod;
    ll ans = 0;
    for(int i = 0;i <= n; i++) {
        int opt = (n - i) & 1 ? -1 : 1;
        ans = (ans + 1ll * opt * pre[i] % mod * suf[i] % mod * invf[i] % mod * invf[n - i] % mod * f[i] % mod + mod) % mod;
    }
    return f[k] = ans;
}


void solve()
{
    init();
    int T;
    cin >> T;
    while(T--) {
        int n, q;
        cin >> n >> q;
        for (int i = 0; i <= n; i++) cin >> F[i];
        F[n + 1] = Lagrange(F, n + 1, n);
        sum[0] = F[0];
        for (int i = 1; i <= n + 1; i++) sum[i] = (sum[i - 1] + F[i]) % mod;
        while (q--) {
            int l, r;
            cin >> l >> r;
            cout << (Lagrange(sum, r, n + 1) - Lagrange(sum, l - 1, n + 1) + mod) % mod << endl;
        }
    }
}

signed main() {
    ios_base::sync_with_stdio(false);
    //cin.tie(nullptr);
    //cout.tie(nullptr);
#ifdef FZT_ACM_LOCAL
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
    signed test_index_for_debug = 1;
    char acm_local_for_debug = 0;
    do {
        if (acm_local_for_debug == '$') exit(0);
        if (test_index_for_debug > 20)
            throw runtime_error("Check the stdin!!!");
        auto start_clock_for_debug = clock();
        solve();
        auto end_clock_for_debug = clock();
        cout << "Test " << test_index_for_debug << " successful" << endl;
        cerr << "Test " << test_index_for_debug++ << " Run Time: "
             << double(end_clock_for_debug - start_clock_for_debug) / CLOCKS_PER_SEC << "s" << endl;
        cout << "--------------------------------------------------" << endl;
    } while (cin >> acm_local_for_debug && cin.putback(acm_local_for_debug));
#else
    solve();
#endif
    return 0;
}


  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值