分治FFT学习笔记

版权声明:_ https://blog.csdn.net/lunch__/article/details/85005705

多项式的坑从暑假之后就再也没有碰过了,暑假还会推导的FFT什么的现在连板子都不会打了,老年退役选手既视感…

前置技能:CDQ分治NTT快速数论变换

有时候我们会遇到一类这样子的递推式:

f i = j = 1 i f i j g j f_i =\displaystyle \sum_{j=1}^if_{i-j}g_j

我们直接做是 O ( n 2 ) O(n^2) 的,但是我们发现这是一个卷积的形式,于是我们可以用FFT做到 O ( n 2 log n ) O(n^2\log n) ,但是FFT对这种式子真的没有用武之地吗,答案是否定的,我们可以用一种分治的方法来解决这个问题。

联想我们之前学过的CDQ分治,我们是分治的时候计算左半部分对右半部分的影响,那么对于这个式子我们也可以这样子来考虑,当我们得出了 f l > m i d f_{l->mid} 的值的时候,可以把这部分对 f m i d + 1 > r f_{mid+1->r} 的贡献计算出来,具体来说:
w i + j = i = l m i d j = 1 r l f i g j [ ( i + j ) [ m i d + 1 , r ] ] w_{i+j}=\sum_{i=l}^{mid}\sum_{j=1}^{r-l}f_ig_j[(i+j)\in[mid+1,r]]

那么我们在分治的时候进行一遍FFT把贡献都算出来,那么复杂度就可以优化到 O ( n log 2 n ) O(n\log^2n) ,那么就足以通过 1 0 5 10^5 的数据了。

洛谷模板
一些有关常数的优化:每次FFT的时候,我们只需要做一遍区间长度的FFT,而不是做一遍长度为 n n 的FFT,这样子我们只需要把 f f 数组往左平移 l l 个单位, g g 数组往左平移 1 1 个单位,算贡献的时候就取往左平移后的贡献即可。

Codes

#include <bits/stdc++.h>

using namespace std;

const int N = 1 << 18 | 1;
const int mod = 998244353;

int A[N], B[N], f[N], g[N], inv[N], rev[N];

inline int read() {
    int _ = 0, __ = getchar(), ___ = 1; 
    for (; !isdigit(__); __ = getchar()) if (__ == '-') ___ = -1;
    for (; isdigit(__); __ = getchar()) _ = (_ << 3) + (_ << 1) + (__ ^ 48);
    return _ * ___;
}

inline int qpow(int a, int x) {
    int ret = 1; 
    for (; x; x >>= 1, a = 1ll * a * a % mod)
        if (x & 1) ret = 1ll * ret * a % mod;
    return ret; 
}

inline void NTT(int *a, int n, int fh) {
    for (int i = 0; i < n; ++ i) 
        if (i < rev[i]) swap(a[i], a[rev[i]]);
    for (int limit = 2; limit <= n; limit <<= 1) {
        int wn1 = qpow((fh ^ 1 ? inv[3] : 3), (mod - 1) / limit), w = 1;
        for (int j = 0; j < n; j += limit, w = 1) 
            for (int i = j; i < j + (limit >> 1); ++ i, w = 1ll * w * wn1 % mod) {
                int a1 = a[i], a2 = 1ll * a[i + (limit >> 1)] * w % mod;
                a[i] = (a1 + a2) % mod, a[i + (limit >> 1)] = (a1 - a2 + mod) % mod;
            }
    }
    if (fh ^ 1) for (int i = 0; i < n; ++ i) 
        a[i] = 1ll * a[i] * inv[n] % mod; 
}

inline void calc(int *a, int *b, int n) {
    NTT(a, n, 1), NTT(b, n, 1);
    for (int i = 0; i < n; ++ i) 
        a[i] = 1ll * a[i] * b[i] % mod;
    NTT(a, n, -1);
}

inline void cdq(int l, int r) {
    if (l == r) return;
    int mid = (l + r) >> 1, limit = 1, k = 0, n = r - l; 
    cdq(l, mid);
    while (limit <= n) limit <<= 1, ++ k;
    for (int i = 0; i < limit; ++ i) {
        A[i] = B[i] = 0;
        rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (k - 1));
    }
    for (int i = l; i <= mid; ++ i) A[i - l] = f[i];
    for (int i = 1; i <= n; ++ i) B[i - 1] = g[i];
    calc(A, B, limit);
    for (int i = mid + 1; i <= r; ++ i) 
        (f[i] += A[i - l - 1]) %= mod;
    cdq(mid + 1, r);
}

int main() {
#ifdef ylsakioi
    freopen("4721.in", "r", stdin);
    freopen("4721.out", "w", stdout);
#endif

    int n; 

    inv[1] = f[0] = 1, n = read(); 
    for (int i = 2; i < N; ++ i) 
        inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;

    for (int i = 1; i < n; ++ i) 
        g[i] = read();
    cdq(0, n - 1);
    for (int i = 0; i < n; ++ i) 
        printf("%d ", f[i]);

    return 0;
}


猜你喜欢

转载自blog.csdn.net/lunch__/article/details/85005705