[COGS2287][HZOI 2015]疯狂的机器人(NTT+组合数学)

本文介绍了一种使用快速傅立叶变换(FFT)优化动态规划(DP)的问题解决方法。通过对特定DP问题进行分析,利用FFT加速卷积计算,从而高效求解方案数。文章详细解释了从问题定义到代码实现的全过程。

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

题目描述

传送门

题解

本来是想找一道题想一想dp到底是怎么用FFT优化的,然而写完了发现怎么和dp没啥关系呢
也可能是我太弱根本没看出来dp
不过确实是一道好题!在hxy神犇的一些提示下做出来…

g(i) 表示只能上下走,不能左右走,不能不走,走i步最后回到原点的方案数
首先可以发现当i为奇数的时候 g(i) 一定为0
接着考虑i为偶数的情况,很显然向上走的步数和向下走的步数一定相等,都为 i2 。求 g(i) 也就是求解这样一个问题:向上走 i2 步,向下走 i2 步,并且必须保证在任何时刻向上走的步数大于等于向下走的步数
这不就是卡特兰数问题嘛,一个组合数公式 g(i)=Ci2iCi2+1i
显然只能左右走,不能上下走,不能不走,走i步回到原点的方案数也是 g(i)

f(i) 表示只能上下左右走,不能不走,走i步最后回到原点的方案数, f(i) 可以写成
f(i)=j=0ig(ij)g(j)Cji
也就是说先选出一部分只能上下走,剩下的只能左右走
将组合数展开,令 g(i)=g(i)i! 能搞成一个卷积的形式
f(i)=j=0ig(ij)g(j)i!j!(ij)!=i!j=0ig(ij)(ij)!g(j)j!=i!j=0ig(ij)g(j)
然后用NTT就可以求出 f(i)

最后考虑将不走的添加进去,即 ans=i=0nf(i)Cin

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define LL long long
#define N 300005

const LL Mod=998244353LL;
int n,m,L,R[N];
LL mul[N],g[N],a[N],b[N],f[N],ans;

void calc_mul(int n)
{
    mul[0]=1LL;
    for (int i=1;i<=n;++i) mul[i]=mul[i-1]*(LL)i%Mod;
}
LL fast_pow(LL a,LL p)
{
    LL ans=1LL;
    for (;p;p>>=1,a=a*a%Mod)
        if (p&1)
            ans=ans*a%Mod;
    return ans;
}
LL C(int n,int m)
{
    if (m>n) return 0LL;
    return mul[n]*fast_pow(mul[m]*mul[n-m]%Mod,Mod-2)%Mod;
}
void NTT(LL a[N],int n,int opt)
{
    for (int i=0;i<n;++i)
        if (i<R[i]) swap(a[i],a[R[i]]);
    for (int k=1;k<n;k<<=1)
    {
        LL wn=fast_pow(3,(Mod-1)/(k<<1));
        for (int i=0;i<n;i+=(k<<1))
        {
            LL w=1;
            for (int j=0;j<k;++j,w=w*wn%Mod)
            {
                LL x=a[i+j],y=w*a[i+j+k]%Mod;
                a[i+j]=(x+y)%Mod,a[i+j+k]=(x-y+Mod)%Mod;
            }
        }
    }
    if (opt==-1) reverse(a+1,a+n);
}
int main()
{
    scanf("%d",&n);
    calc_mul(n);
    for (int i=1;i<=n;++i)
    {
        if (i&1) continue;
        g[i]=C(i,i/2)-C(i,i/2+1);
        g[i]=(g[i]+Mod)%Mod;
    }g[0]=1LL;
    for (int i=1;i<=n;++i) g[i]=g[i]*fast_pow(mul[i],Mod-2)%Mod;

    for (int i=0;i<=n;++i) a[i]=b[i]=g[i];
    m=n+n;
    for (n=1;n<=m;n<<=1) ++L;
    for (int i=0;i<n;++i)
        R[i]=(R[i>>1]>>1)|((i&1)<<(L-1));
    NTT(a,n,1);NTT(b,n,1);
    for (int i=0;i<=n;++i) a[i]=a[i]*b[i]%Mod;
    NTT(a,n,-1);
    LL inv=fast_pow(n,Mod-2);
    for (int i=0;i<=n;++i) a[i]=a[i]*inv%Mod;

    for (int i=0;i<=m/2;++i) f[i]=mul[i]*a[i]%Mod;
    for (int i=0;i<=m/2;++i) ans=(ans+f[i]*C(m/2,i)%Mod)%Mod;
    printf("%I64d\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值