[COGS2287][HZOI2015]疯狂的机器人(NTT+Catalan数)

15 篇文章 0 订阅
15 篇文章 0 订阅

题目:

我是超链接

题解:

又是上下左右走的问题。。。当初我的Catalan入门就是这玩意

那么我们设g[i]表示走i步光走左右不走上下,不会不走,回到原点的方案数
可以发现当i为奇数的时候g[i]=0
考虑i为偶数的情况,肯定是选择i/2步向上走,i/2步向下走,并且时刻保证向上走的步数>=向下走的步数
这不就是Catalan数吗,可以用 Ci/2iCi/21i C i i / 2 − C i i / 2 − 1 求解

显然光走上下不走左右,不会不走,回到原点的方案也是g[i]

现在我们设f[i]表示走i步,可以走上下左右,不会不走,回到原点的方案数
那么 f[i]=ij=0g(j)g(ij)Cji f [ i ] = ∑ j = 0 i g ( j ) ∗ g ( i − j ) ∗ C i j
我们把组合数展开

f[i]=j=0ig(j)j!g(ij)(ij)!i! f [ i ] = ∑ j = 0 i g ( j ) j ! ∗ g ( i − j ) ( i − j ) ! ∗ i !

卷积!又看到了熟悉的模数,NTT吧
那么如果有不走的情况呢? ans=ni=0f[i]Cin a n s = ∑ i = 0 n f [ i ] ∗ C n i

代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
#define LL long long
using namespace std;
const int mod=998244353;
const int N=400005;
LL a[N],b[N],mul[N],inv[N],invmul[N],g[N];int r[N],n;
LL ksm(LL a,LL k)
{
    LL ans=1;
    for (;k;k>>=1,a=a*a%mod)
      if (k&1) ans=ans*a%mod;
    return ans;
}
void NTT(LL *a,int id)
{
    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=ksm(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 (id==-1) reverse(a+1,a+n);
}
LL C(int n,int m)
{
    if (m>n) return 0;
    return mul[n]*invmul[m]%mod*invmul[n-m]%mod;
}
void init(int n)
{
    mul[0]=mul[1]=1;
    for (int i=2;i<=n;i++) mul[i]=mul[i-1]*i%mod;
    inv[1]=1;
    for (int i=2;i<=n;i++) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
    invmul[0]=1;
    for (int i=1;i<=n;i++) invmul[i]=invmul[i-1]*inv[i]%mod;
    for (int i=0;i<=n;i+=2) 
      g[i]=(C(i,i/2)-C(i,i/2+1)+mod)%mod;
    g[0]=1;
    for (int i=0;i<=n;i++) a[i]=b[i]=g[i]*invmul[i]%mod;
}
int main()
{
    freopen("crazy_robot.in","r",stdin);
    freopen("crazy_robot.out","w",stdout);
    int fn;scanf("%d",&fn);init(fn);
    int L=0;
    for (n=1;n<=fn*2;n<<=1) L++;
    for (int i=0;i<n;i++) r[i]=(r[i>>1]>>1)|((i&1)<<L-1);
    NTT(a,1); NTT(b,1);
    for (int i=0;i<=n;i++) a[i]=a[i]*b[i]%mod;
    NTT(a,-1);LL inv=ksm(n,mod-2);
    for (int i=0;i<=n;i++) a[i]=a[i]*inv%mod;
    for (int i=0;i<=fn;i++) a[i]=a[i]*mul[i]%mod;
    LL ans=0;
    for (int i=0;i<=fn;i++) ans=(ans+a[i]*C(fn,i)%mod)%mod;
    printf("%lld",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值