[BZOJ4555][Tjoi2016&Heoi2016]求和-NTT-多项式求逆

求和

Description

在2016年,佳媛姐姐刚刚学习了第二类斯特林数,非常开心。

现在他想计算这样一个函数的值:
pic1
S(i, j)表示第二类斯特林数,递推公式为:
S(i, j) = j ∗ S(i − 1, j) + S(i − 1, j − 1), 1 <= j <= i − 1。
边界条件为:S(i, i) = 1(0 <= i), S(i, 0) = 0(1 <= i)
你能帮帮他吗?

Input

输入只有一个正整数

Output

输出f(n)。由于结果会很大,输出f(n)对998244353(7 × 17 × 223 + 1)取模的结果即可。1 ≤ n ≤ 100000

Sample Input

3

Sample Output

87


多项式求逆……
真是丧心病狂啊……


思路:
直接拆式子貌似不太好拆。

于是考虑换一种方法表示原问题:
g(n)=i=1n{ni}2ii! ,求其前缀和。

考虑 g(n) 的组合意义,代表将 n 1n的数分成若干集合,且满足每个集合有两种状态的方案数。
所以可以把式子写成下面这样:

g(n)=i=1n2(ni)g(ni)

即枚举第一个集合的方案数乘以剩余集合的方案数。

考虑向卷积靠近:

g(n)n!=i=1n2i!g(ni)(ni)!

指数型生成函数!

于是构造如下生成函数:

G=i=0g(i)i!
F=i=12i!

那么有如下式子:
G(x)=F(x)G(x)+1
注意这里的 1 实际上指的是,由于G(x) F(x) 的起始下标不同,选择以 1 为下标开始卷积,而导致需要舍弃掉g(n0)(n0)!一项而补上的 F(0) 的系数。

然后有

G(x)=11F(x)

然后构造出 F(x) 并多项式求逆即可得到 G(x) 的系数,求和得到答案~

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>

using namespace std;

typedef long long ll;
const int N=400009;
const ll md=998244353;

int n,m;
ll G[N],F[N],C[N];
ll fac[N],inv[N];
int rev[N];

inline ll qpow(ll a,ll b)
{
    ll ret=1;
    while(b)
    {
        if(b&1)
            ret=ret*a%md;
        a=a*a%md;
        b>>=1;
    }
    return ret;
}

inline void NTT(ll *a,int n,int f)
{
    for(int i=0;i<n;i++)if(i<rev[i])swap(a[i],a[rev[i]]);
    for(int h=2;h<=n;h<<=1)
    {
        ll w=qpow(3,(md-1)/h);
        if(f)w=qpow(w,md-2);
        for(int j=0;j<n;j+=h)
        {
            ll wn=1ll;
            for(int k=j;k<j+(h>>1);k++)
            {
                ll x=a[k],y=wn*a[k+(h>>1)]%md;
                a[k]=(x+y)%md;
                a[k+(h>>1)]=(x-y+md)%md;
                wn=wn*w%md;
            }
        }
    }
    if(f)
        for(ll i=0,invs=qpow(n,md-2);i<n;i++)
            a[i]=a[i]*invs%md;
}

inline void cinv(ll *a,ll *b,int n)
{
    if(n==0)
    {
        b[0]=qpow(a[0],md-2);
        return;
    }

    cinv(a,b,n>>1);n<<=1;

    for(int i=0;i<n;i++)
        rev[i]=(rev[i>>1]>>1)|((i&1)*(n>>1));
    for(int i=0;i<n/2;i++)
        C[i]=a[i],C[i+n/2]=0;

    NTT(C,n,0);NTT(b,n,0);
    for(int i=0;i<n;i++)
        b[i]=(2*b[i]%md-C[i]*b[i]%md*b[i]%md+md)%md;
    NTT(b,n,1);

    for(int i=n/2;i<n;i++)b[i]=0;
}

int main()
{
    scanf("%d",&m);
    for(n=1;n<=m;n<<=1);

    fac[0]=1;
    for(ll i=1;i<=n;i++)
        fac[i]=fac[i-1]*i%md;
    inv[n]=qpow(fac[n],md-2);
    for(ll i=n;i>=1;i--)
        inv[i-1]=inv[i]*i%md;

    for(int i=1;i<=m;i++)
        F[i]=(md-inv[i])*2%md;
    F[0]=1;
    cinv(F,G,n);

    ll ans=0;
    for(int i=m;i>=0;i--)
        (ans+=G[i]*fac[i]%md)%=md;
    printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值