【JSOI 2012】分零食(倍增 + FFT)

本文介绍了一道名为“分零食”的算法竞赛题,通过动态规划与快速傅里叶变换(FFT)结合的方法来高效解决该问题。具体讲解了如何利用FFT进行卷积运算,并给出了完整的C++实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接:【JSOI 2012】分零食

题目大意:将 m m 颗糖依次分给 n 个小朋友,每个小朋友的快乐度为 f(x=)=ox2+sx+u f ( x = 得 到 的 糖 果 数 ) = o x 2 + s x + u 。特别地, f(0)=1 f ( 0 ) = 1 。糖果分完后剩下的小朋友将得不到糖果。求每种情况下小朋友快乐度乘积之和 mod998244353 mod 998244353 的结果。

g[i][j] g [ i ] [ j ] 表示前 i i 个小朋友得到 j 颗糖的答案。则: g[i][j]=jk=1f(jk)g[i1][k] g [ i ] [ j ] = ∑ k = 1 j f ( j − k ) g [ i − 1 ] [ k ] 。发现这是个卷积的形式。想到 FFT F F T ,发现 g[n]=fn g [ n ] = f n

A[n]=nj=1g[i] A [ n ] = ∑ j = 1 n g [ i ] 。我们使用倍增的方法求出 A[n] A [ n ]


A[n+1]=n+1i=1g[i] A [ n + 1 ] = ∑ i = 1 n + 1 g [ i ]
A[n+1]=A[n]+g[n+1] A [ n + 1 ] = A [ n ] + g [ n + 1 ]

A[2n]=2ni=1g[i] A [ 2 n ] = ∑ i = 1 2 n g [ i ]
A[2n]=A[n]+2ni=n+1g[i] A [ 2 n ] = A [ n ] + ∑ i = n + 1 2 n g [ i ]
A[2n]=A[n]+fnni=1fi A [ 2 n ] = A [ n ] + f n ∑ i = 1 n f i
A[2n]=A[n]+g[n]A[n] A [ 2 n ] = A [ n ] + g [ n ] ∗ A [ n ]


然后用 FFT F F T 做卷积即可。

#include <cmath>
#include <cstdio>
#include <complex>
#include <iostream>
using namespace std;
typedef complex<double> cd;
const double pi = acos(-1.);
const int maxn = 1 << 15 | 5;
cd f[maxn], g[maxn], dp[maxn], tmp[maxn];
int n, m, o, s, u, mod, lim, bit, r[maxn];
void fft(cd *a, int dft) {
    for (int i = 0; i < lim; i++) {
        if (i < r[i]) {
            swap(a[i], a[r[i]]);
        }
    }
    for (int k = 1; k < lim; k <<= 1) {
        cd wn0(cos(pi / k), dft * sin(pi / k));
        for (int i = 0; i < lim; i += k << 1) {
            cd wnk(1, 0);
            for (int j = i; j < i + k; j++, wnk *= wn0) {
                cd x = a[j], y = wnk * a[j + k];
                a[j] = x + y, a[j + k] = x - y;
            }
        }
    }
    if (dft == -1) {
        for (int i = 0; i < lim; i++) {
            a[i] /= lim;
            a[i] = int(a[i].real() + 0.5) % mod;
        }
    }
}
void solve(cd *dp, cd *g, int n) {
    if (!n) {
        g[0] = 1;
        return;
    }
    if (n & 1) {
        solve(dp, g, n - 1);
        fft(g, 1);
        for (int i = 0; i < lim; i++) {
            g[i] *= f[i];
        }
        fft(g, -1);
        for (int i = 0; i <= m; i++) {
            dp[i] += g[i];
            dp[i] = int(dp[i].real() + 0.5) % mod; 
        }
        for (int i = m + 1; i < lim; i++) {
            g[i] = 0;
        }
    } else {
        solve(dp, g, n >> 1);
        for (int i = 0; i < lim; i++) {
            tmp[i] = dp[i];
        }
        fft(tmp, 1);
        fft(g, 1);
        for (int i = 0; i < lim; i++) {
            tmp[i] *= g[i];
        }
        fft(tmp, -1);
        for (int i = 0; i < lim; i++) {
            g[i] *= g[i];
        }
        fft(g, -1);
        for (int i = 0; i <= m; i++) {
            dp[i] += tmp[i];
            dp[i] = int(dp[i].real() + 0.5) % mod; 
        }
        for (int i = m + 1; i < lim; i++) {
            g[i] = 0;
        }
    }
}
int main() {
    scanf("%d %d", &m, &mod);
    scanf("%d", &n);
    scanf("%d", &o);
    scanf("%d", &s);
    scanf("%d", &u);
    for (lim = 1; lim <= 2 * m; lim <<= 1)  bit++;
    for (int i = 0; i < lim; i++) {
        r[i] = (r[i >> 1] >> 1) | ((i & 1) << (bit - 1));
    }
    for (int i = 1; i <= m; i++) {
        f[i] = (o * i * i + s * i + u) % mod;
    }
    fft(f, 1);
    solve(dp, g, n);
    printf("%d\n", int(dp[m].real() + 0.5));
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值