分治FFT

分治FFT

目的

解决这样一类式子:
\[f[n] = \sum_{i = 0}^{n - 1}f[i]g[n - i]\]

算法

看上去跟普通卷积式子挺像的,但是由于计算\(f\)的每一项时都在利用它前面的项来产生贡献,所以不能一次FFT搞完。用FFT爆算复杂度\(O(n^2logn)\),比直接枚举复杂度还高……
考虑优化这个算法,如果我们要计算区间\([l, r]\)内的\(f\)值,如果可以快速算出区间\([l, mid]\)内的\(f\)值对区间\([mid + 1, r]\)内的\(f\)值产生了怎样的影响,就可以采取CDQ分治,不断递归下去算。

考虑\(x \in [mid + 1, r]\),\([l, mid]\)给它的贡献是:
\[h[x] = \sum_{i = l}^{mid}f[i]g[x - i]\]
为了方便,我们将范围扩充到\([1, x - 1]\)(假设此时\(f[mid + 1] ... f[r] = 0\)),因此有:
\[h[x] = \sum_{i = l}^{x - 1}f[i]g[x - i]\]
为了便于FFT计算,将枚举改成从0开始。(把表达式中的\(i\)改成\(i + l\),因为原来的\(i\)等于现在的\(i + l\))
\[h[x] = \sum_{i = 0}^{x - l - 1}f[i + l]g[x - l - i]\]
为了表示成卷积形式,我们令:
\[a[i] = f[i + l], b[i - 1] = g[i]\]
再在原式中用\(a[i], b[i]\)代替\(f[i], g[i]\).
\[h[x] = \sum_{i = 0}^{x - l - 1}a[i] b[x - l - 1 - i]\]
观察到后面刚好就是多项式乘法中某一项的系数,即
\[h[x] = (a * b)(x - l - 1)\]
在cdq分治的过程中用FFT/NTT计算即可。

代码

洛谷上的模板,因为要取模,所以用的NTT

#include<bits/stdc++.h>
using namespace std;
#define R register int
#define p 998244353
#define AC 400100
#define LL long long
#define ld double

const int G = 3, Gi = 332748118;
int n, lim, len;
int f[AC], g[AC], a[AC], b[AC], rev[AC];

inline int read()
{
    int x = 0;char c = getchar();
    while(c > '9' || c < '0') c = getchar();
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x;
}

inline void up(int &a, int b) {a += b; if(a < 0) a += p; if(a >= p) a -= p;}

inline int qpow(int x, int have)
{
    int rnt = 1;
    while(have)
    {
        if(have & 1) rnt = 1LL * rnt * x % p;
        x = 1LL * x * x % p, have >>= 1;
    }
    return rnt;
}

void init(int length)//这里的length已经是2个数组加起来的长度了
{
    lim = 1, len = 0;
    while(lim <= length) lim <<= 1, ++ len;
    for(R i = 0; i < lim; i ++)
        rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (len - 1)), a[i] = b[i] = 0;
}

void NTT(int *A, int opt)
{
    for(R i = 0; i < lim; i ++)
        if(i < rev[i]) swap(A[i], A[rev[i]]);
    for(R i = 1; i < lim; i <<= 1)
    {
        LL W = qpow((opt > 0) ? G : Gi, (p - 1) / (i << 1));
        for(R r = i << 1, j = 0; j < lim; j += r)
            for(R k = 0, w = 1; k < i; k ++, w = 1LL * w * W % p)
            {
                int x = A[j + k], y = 1LL * w * A[j + k + i] % p;
                A[j + k] = (x + y) % p, A[j + k + i] = (x - y) % p;
            }
    }
    if(opt == -1)
    {
        int inv = qpow(lim, p - 2);
        for(R i = 0; i < lim; i ++) A[i] = 1LL * A[i] * inv % p;
    }
}

void pre()
{
    n = read(), f[0] = 1;
    for(R i = 1; i < n; i ++) g[i] = read();
}

void cal(int *A, int *B)
{
    NTT(A, 1), NTT(B, 1);
    for(R i = 0; i <= lim; i ++) A[i] = 1LL * A[i] * B[i] % p;
    NTT(A, -1);
}

void cdq(int l, int r)
{
    if(l == r) return ;
    int mid = (l + r) >> 1, length = r - l + 1;
    cdq(l, mid);
    init(length);
    for(R i = l; i <= mid; i ++) a[i - l] = f[i];
    for(R i = 1; i < length; i ++) b[i - 1] = g[i];//这里要移动是为了凑x - l - 1
    cal(a, b);
    for(R i = mid + 1; i <= r; i ++) up(f[i], a[i - l - 1]);
    cdq(mid + 1, r);
}

int main()
{
    freopen("in.in", "r", stdin);
    pre();
    cdq(0, n - 1);
    for(R i = 0; i < n; i ++) printf("%d ", ((f[i] % p) + p) % p);
    printf("\n");
    fclose(stdin);
    return 0;
}

原文地址:https://www.cnblogs.com/ww3113306/p/10359556.html

时间: 2024-08-29 07:12:06

分治FFT的相关文章

2017 3 11 分治FFT

考试一道题的递推式为$$f[i]=\sum_{j=1}^{i} j^k \times (i-1)! \times \frac{f[i-j]}{(i-j)!}$$这显然是一个卷积的形式,但$f$需要由自己卷过来(我也不知到怎么说),以前只会生成函数的做法,但这题好像做不了(谁教教我怎么做),于是无奈的写了一发暴力,看题解发现是分治FFT.分治每层用$f[l]-f[mid]$与$a[1]-a[r-l]$做NTT.这样显然每个$f[l]-f[mid]$对$f[mid+1]-f[r]$的贡献都考虑到了.

【bzoj4836】[Lydsy2017年4月月赛]二元运算 分治+FFT

题目描述 定义二元运算 opt 满足 现在给定一个长为 n 的数列 a 和一个长为 m 的数列 b ,接下来有 q 次询问.每次询问给定一个数字 c 你需要求出有多少对 (i, j) 使得 a_i  opt b_j=c . 输入 第一行是一个整数 T (1≤T≤10) ,表示测试数据的组数. 对于每组测试数据: 第一行是三个整数 n,m,q (1≤n,m,q≤50000) . 第二行是 n 个整数,表示 a_1,a_2,?,a_n (0≤a_1,a_2,?,a_n≤50000) . 第三行是 m

HDU Shell Necklace CDQ分治+FFT

Shell Necklace Problem Description Perhaps the sea‘s definition of a shell is the pearl. However, in my view, a shell necklace with n beautiful shells contains the most sincere feeling for my best lover Arrietty, but even that is not enough. Suppose

看无可看 分治FFT+特征值方程

题面: 看无可看(see.pas/cpp/c) 题目描述 “What’s left to see when our eyes won’t open?” “若彼此瞑目在即,是否终亦看无可看?” ------来自网易云音乐<Golden Leaves-Passenger> 最后的一刻我看到了...... 一片昏暗? 我记起来了, 我看到,那里有一个集合S,集合S中有n个正整数a[i](1<=i<=n) 我看到,打破昏暗的密码: 记忆中的f是一个数列,对于i>1它满足f(i)=2*

【BZOJ3451】Tyvj1953 Normal 点分治+FFT+期望

[BZOJ3451]Tyvj1953 Normal Description 某天WJMZBMR学习了一个神奇的算法:树的点分治!这个算法的核心是这样的:消耗时间=0Solve(树 a) 消耗时间 += a 的 大小 如果 a 中 只有 1 个点  退出 否则在a中选一个点x,在a中删除点x 那么a变成了几个小一点的树,对每个小树递归调用Solve我们注意到的这个算法的时间复杂度跟选择的点x是密切相关的.如果x是树的重心,那么时间复杂度就是O(nlogn)但是由于WJMZBMR比较傻逼,他决定随机

[BZOJ4555][TJOI2016&amp;HEOI2016]求和(分治FFT)

解法一:容易得到递推式,可以用CDQ分治+FFT 代码用时:1h 比较顺利,没有低级错误. 实现比较简单,11348ms #include<cstdio> #include<algorithm> #define rep(i,l,r) for (int i=l; i<=r; i++) typedef long long ll; using namespace std; const int N=(1<<18)+100,P=998244353,g=3; int n,re

【XSY2166】Hope 分治 FFT

题目描述 对于一个\(1\)到\(n\)的排列\(a_1,a_2,a_3,\ldots,a_n\),我们定义这个排列的\(P\)值和\(Q\)值: 对于每个\(a_i\),如果存在一个最小的\(j\)使得\(i<j\)且\(a_i<a_j\),那么将\(a_i\)和\(a_j\)连一条无向边.于是就得到一幅图.计算这幅图每个联通块的大小,将它们相乘,得到\(P\).记\(Q=P^k\). 对于\(1\)到\(n\)的所有排列,我们想知道它们的\(Q\)值之和.由于答案可能很大,请将答案对\(9

【XSY2744】信仰圣光 分治FFT 多项式exp 容斥原理

题目描述 有一个\(n\)个元素的置换,你要选择\(k\)个元素,问有多少种方案满足:对于每个轮换,你都选择了其中的一个元素. 对\(998244353\)取模. \(k\leq n\leq 152501\) 题解 吐槽 为什么一道FFT题要把\(n\)设为\(150000\)? 解法一 先把轮换拆出来. 直接DP. 设\(f_{i,j}\)为前\(i\)个轮换选择了\(j\)个元素,且每个轮换都选择了至少一个元素的方案数. \[ f_{i,j}=\sum_{k=1}^{a_i}f_{i-1,j

hdu5730 Shell Necklace 【分治fft】

题目 简述: 有一段长度为n的贝壳,将其划分为若干段,给出划分为每种长度的方案数,问有多少种划分方案 题解 设\(f[i]\)表示长度为\(i\)时的方案数 不难得dp方程: \[f[i] = \sum\limits_{j=0}^{i} a[j] * f[i - j]\] 考虑转移 直接转移是\(O(n^2)\)的 如何优化? 容易发现这个转移方程非常特别,是一个卷积的形式 考虑fft 分治fft 分治fft解决的就是这样一个转移方程的快速计算的问题 \[f[i] = \sum\limits_{

bzoj千题计划309:bzoj4332: JSOI2012 分零食(分治FFT)

https://www.lydsy.com/JudgeOnline/problem.php?id=4332 因为如果一位小朋友得不到糖果,那么在她身后的小朋友们也都得不到糖果. 所以设g[i][j] 表示前i位小朋友,分到j个糖果,且前i位小朋友都分到糖果的方案数 令F(x) 表示分到x个糖果的欢乐程度 ∴g[i][j] = ∑ g[i-1][j-k]*F(k) 记g[i]=g[i-1]*F,则 g[i]=F ^ i 但是要求的是 Σ g[i][m] 记f[n]=Σ g[i]  i∈[1,n]