cogs 2287. [HZOI 2015]疯狂的机器人 (NTT优化DP)

本文介绍了一道名为“疯狂的机器人”的竞赛题目的解决方案,该题目要求计算机器人在特定条件下回到原点的操作序列数量。采用卡特兰数理论结合快速傅立叶变换(NTT)优化计算过程。

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

题目描述

  1. [HZOI 2015]疯狂的机器人
    ★★★ 输入文件:crazy_robot.in 输出文件:crazy_robot.out 简单对比
    时间限制:1 s 内存限制:512 MB
    【题目描述】

现在在二维平面内原点上有一只机器人
他每次操作可以选择向右走,向左走,向下走,向上走和不走(每次如果走只能走一格)
但是由于本蒟蒻施展的大魔法,机器人不能走到横坐标是负数或者纵坐标是负数的点上
否则他就会big bang
给定操作次数n,求有多少种不同的操作序列使得机器人在操作后会回到原点
输出答案模998244353后的结果
注意如果两个操作序列存在某一时刻操作不同,则我们认为这两个操作序列不同
【输入格式】

输入n,表示操作次数
n<=100000
【输出格式】

按要求输出答案
【样例输入】

3
【样例输出】

7
【提示】

样例解释:
机器人有7种操作序列
1、不走 不走 不走
2、不走 向右 向左
3、向右 不走 向左
4、向右 向左 不走
5、不走 向上 向下
6、向上 不走 向下
7、向上 向下 不走

题解

因为最终要回到原点,所以向上和向下的步数相等,向左和向右的步数相等。
需要保证在行走的过程中,不能走到负数区域,那么就是在单独看向上和向下两种走法时,任意时刻向上走的步数大于等于向下走的步数,向左和向右也是同理。
这是一个经典的卡特兰数问题,如果向上和向下一共走了i步,那么单考虑这两种走法,方案数g[i]Ci/2iCi/2+1i。那么向左向右也是同理。
设一共走了i步那么不考虑不走的情况,总方案数为
f[j]=ji=0g[i]g[ji]Cij
=ji=0g[i]i!g[ji](ji)!j!
然后发现是卷积的形式,由于998244353是费马质数,所以我可以用NTT来加速。
最后我们只需要考虑上原地不动的就可以
ans=i=0nf[i]Cin

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#define N 500030
#define LL long long
#define p 998244353
using namespace std;
int n,n1,m,L;
LL f[N],g[N],jc[N],R[N];
LL quickpow(LL num,LL x)
{
    LL base=num%p; LL ans=1;
    while (x) {
        if (x&1) ans=ans*base%p;
        x>>=1;
        base=base*base%p;
    }
    return ans;
}
LL calc(LL n,LL m)
{
    if (n<m) return 0;
    return jc[n]*quickpow(jc[n-m]*jc[m]%p,p-2)%p;
}
void NTT(LL x1[N],int n,int opt)
{
    int j;
    for (int i=0;i<n;i++) 
     if (i<R[i]) swap(x1[i],x1[R[i]]);
    for (int i=1;i<n;i<<=1) {
        LL wn=quickpow(3,(p-1)/(i<<1));
        for (int p1=i<<1,j=0;j<n;j+=p1) {
            LL w=1;
            for (int k=0;k<i;k++,w=(w*wn)%p) {
                LL x=x1[j+k],y=(w*x1[j+k+i])%p;
                x1[j+k]=(x+y)%p; x1[j+k+i]=(x-y+p)%p;
            }
        }
    }
    if (opt==-1) reverse(x1+1,x1+n);
}
int main()
{
    freopen("crazy_robot.in","r",stdin);
    freopen("crazy_robot.out","w",stdout);
    scanf("%d",&n);
    jc[0]=1;
    for (int i=1;i<=n;i++) jc[i]=(jc[i-1]*(LL)i)%p;
    for (int i=1;i<=n;i++){
     if (!(i&1)) f[i]=calc(i,i/2)-calc(i,i/2+1);
     f[i]=(f[i]+p)%p;
    }
    f[0]=1;
    for (int i=0;i<=n;i++) f[i]=f[i]*quickpow(jc[i],p-2)%p;
    m=n*2; n1=0;
    for (n1=1;n1<=m;n1<<=1) L++;
    for (int i=0;i<=n1;i++) R[i]=(R[i>>1]>>1)|((i&1)<<(L-1));
    NTT(f,n1,1); //cout<<n1<<endl;
    for (int i=0;i<=n1;i++) g[i]=f[i];
    for (int i=0;i<=n1;i++) f[i]=(f[i]*g[i])%p;
    NTT(f,n1,-1);
    LL inv=quickpow(n1,p-2); 
    for (int i=0;i<=n1;i++) f[i]=f[i]*inv%p;
    for (int i=0;i<=n1;i++) f[i]=f[i]*jc[i]%p;
    LL ans=0;
    for (int i=0;i<=n;i++)
     ans=(ans+f[i]*calc(n,i)%p)%p;
    printf("%I64d\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值