NKOJ 4028(HZOI 2015)疯狂的机器人(NTT+卡特兰数)

P4028[HZOI 2015]疯狂的机器人

问题描述

现在在二维平面内原点上有一只机器人

他每次操作可以选择向右走,向左走,向下走,向上走和不走(每次如果走只能走一格)

但是由于本蒟蒻施展的大魔法,机器人不能走到横坐标是负数或者纵坐标是负数的点上

否则他就会big bang

给定操作次数n,求有多少种不同的操作序列使得机器人在操作后会回到原点

输出答案模998244353后的结果

注意如果两个操作序列存在某一时刻操作不同,则我们认为这两个操作序列不同

输入格式

输入n,表示操作次数

n<=100000

输出格式

按要求输出答案

样例输入

3

样例输出

3


首先不考虑不走的情况,容易发现上下走和左右走是独立的,而只考虑上下走的话等价于一个进出栈序列,那么就是一个卡特兰数,因此我们令不考虑不走,走 2k 2 k 步的方案数为 G(2k) G ( 2 k ) ,由于卡特兰数第k项为 Ck2kk+1 C 2 k k k + 1 ,容易得到

G(2k)=i=0kCi2ii+1×Cki2k2iki+1×C2i2k G ( 2 k ) = ∑ i = 0 k C 2 i i i + 1 × C 2 k − 2 i k − i k − i + 1 × C 2 k 2 i

即枚举上下走的步数,然后排列一下。前两项分别表示上下走和左右走的方案数。然后打开组合数得到
G(2k)=i=0k(2i)!i!(i+1)!×(2k2i)!(ki)!(ki+1)!×(2k)!(2i)!(2k2i)! G ( 2 k ) = ∑ i = 0 k ( 2 i ) ! i ! ( i + 1 ) ! × ( 2 k − 2 i ) ! ( k − i ) ! ( k − i + 1 ) ! × ( 2 k ) ! ( 2 i ) ! ( 2 k − 2 i ) !

化简一下得到
G(2k)=(2k)!i=0k1i!(i+1)!×1(ki)!(ki+1)! G ( 2 k ) = ( 2 k ) ! ∑ i = 0 k 1 i ! ( i + 1 ) ! × 1 ( k − i ) ! ( k − i + 1 ) !

容易发现这是个卷积,因此可以用NTT来加速。

然后考虑不走的情况,令最终方案数为 F(n) F ( n ) ,容易得到

F(n)=i=0nCin×G(ni) F ( n ) = ∑ i = 0 n C n i × G ( n − i )

这也是个卷积,但只需要算一项,那么直接暴力枚举就行了。也可以NTT。

总复杂度 O(nlogn) O ( n log ⁡ n )


代码:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#define ll long long
#define N 400005
using namespace std;
const ll P=998244353LL,g=3;
ll jc[N],inv[N],inv_jc[N],D[N];
ll wi[N],A[N],B[N];
ll QM(ll a,ll b)
{
    ll o=1;a%=P;
    while(b)
    {
        if(b&1)o=o*a%P;
        b>>=1;a=a*a%P;
    }
    return o;
}
void NTT(ll C[],ll n,ll ty)
{
    ll i,j,k,m,t0,t1;
    for(i=j=0;i<n;i++)
    {
        if(i<j)swap(C[i],C[j]);
        for(k=n>>1;(j^=k)<k;k>>=1);
    }
    wi[0]=1;
    for(m=1;m<n;m<<=1)
    {
        t0=QM(g,P-1+ty*(P-1)/(m<<1));
        for(i=1;i<m;i++)wi[i]=wi[i-1]*t0%P;
        for(k=0;k<n;k+=m<<1)
        for(i=k;i<k+m;i++)
        {
            t0=C[i];
            t1=C[i+m]*wi[i-k]%P;
            C[i]=t0+t1;C[i]-=C[i]<P?0:P;
            C[i+m]=t0-t1;C[i+m]+=C[i+m]<0?P:0;
        }
    }
    if(ty==1)return;t0=QM(n,P-2);
    for(i=0;i<n;i++)C[i]=C[i]*t0%P;
}
int main()
{
    ll n,i,j,k,L=1;inv[0]=jc[0]=inv[1]=jc[1]=inv_jc[0]=inv_jc[1]=A[0]=1;
    scanf("%lld",&n);while(L<=n+n)L<<=1;
    for(i=2;i<=n+n;i++)inv[i]=(P-P/i)*inv[P%i]%P;
    for(i=2;i<=n+n;i++)jc[i]=jc[i-1]*i%P;
    for(i=2;i<=n+n;i++)inv_jc[i]=inv_jc[i-1]*inv[i]%P;
    for(i=0;i<=n;i++)D[i]=inv_jc[i]*inv_jc[i]%P*inv[i+1]%P;
    NTT(D,L,1);
    for(i=0;i<L;i++)D[i]=D[i]*D[i]%P;
    NTT(D,L,-1);
    for(i=0;i<=n;i+=2)B[i]=jc[i]*D[i>>1]%P;
    for(i=1;i<=n;i++)A[i]=A[i-1]*(n-i+1)%P*inv[i]%P;
    NTT(A,L,1);NTT(B,L,1);
    for(i=0;i<L;i++)A[i]=B[i]*A[i]%P;
    NTT(A,L,-1);
    printf("%lld",(A[n]%P+P)%P);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值