BZOJ 4555 HEOI2016/TJOI2016求和

Problem

BZOJ

Solution

发现我的思路比较清奇?可能是我数学太差了想不到

首先可以用容斥推出第二类斯特林数的通项公式(没见过就是真·推不出来)

Smn=1m!i=0m(1)iCim(mi)n=i=0m(1)i(i!)(mi)n(mi)! S n m = 1 m ! ∑ i = 0 m ( − 1 ) i C m i ( m − i ) n = ∑ i = 0 m ( − 1 ) i ( i ! ) ( m − i ) n ( m − i ) !

这是一个卷积的形式,那么我们可以利用NTT在 O(nlogn) O ( n log ⁡ n ) 的时间求出第n行的斯特林数。

我们考虑把 2j×(j!) 2 j × ( j ! ) 看作一个系数,根据递推公式,把斯特林数往下一行推。

然后如果你手动模拟一下,就像这样:

i\j012
01
101
2138
i\j0123
01
101
2004
3131048
i\j01234
01
101
2004
300030
4131054384

其中, Sii S i i 的系数会有剩余,但我们知道它恒等于1,所以只需要知道它的系数即可。

又容易发现,这些系数是有规律的,我们可以用递推来模拟做这样的一个事情,复杂度 O(n) O ( n ) 。转移方程可以参考代码,其中a数组为 sii s i i 的系数,b数组为最后一行的系数,c为第i列向第i+1列的转移系数。唯一需要注意的是第n项的系数是没有得到加成的,还是 2n(n!) 2 n ( n ! )

时间复杂度 O(nlogn) O ( n log ⁡ n )

Code

#include <algorithm>
#include <cstring>
#include <cstdio>
#define rg register
using namespace std;
typedef long long ll;
const int maxn=100010,mod=998244353,G=3;
template <typename Tp> inline void getmin(Tp &x,Tp y){if(y<x) x=y;}
template <typename Tp> inline void getmax(Tp &x,Tp y){if(y>x) x=y;}
template <typename Tp> inline void read(Tp &x)
{
    x=0;char ch=getchar();int f=0;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=1,ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    if(f) x=-x;
}
int n,m,tmp,N=1,l,ans,f[maxn<<2],g[maxn<<2],rev[maxn<<2];
int fac[maxn],inv[maxn],mi[maxn],a[maxn],b[maxn],c[maxn];
inline int pls(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
int power(int x,int y)
{
    int res=1;
    for(;y;y>>=1,x=(ll)x*x%mod)
      if(y&1)
        res=(ll)res*x%mod;
    return res;
}
void init()
{
    fac[0]=mi[0]=1;
    for(rg int i=1;i<=n;i++)
      fac[i]=(ll)fac[i-1]*i%mod,mi[i]=pls(mi[i-1],mi[i-1]);
    inv[n]=power(fac[n],mod-2);
    for(rg int i=n-1;~i;i--) inv[i]=(ll)inv[i+1]*(i+1)%mod;
    a[0]=1;b[0]=1;c[0]=1;
    for(rg int i=1;i<=n;i++)
    {
        a[i]=dec((ll)fac[i]*mi[i]%mod,(ll)i*c[i-1]%mod);
        b[i]=pls((ll)fac[i]*mi[i]%mod,c[i-1]%mod);
        c[i]=dec(b[i],(ll)i*c[i-1]%mod);
    }
    for(rg int i=0;i<n;i++) ans=pls(ans,a[i]);
}
void ntt(int *a,int f)
{
    for(rg int i=0;i<N;i++)
      if(i<rev[i]) swap(a[i],a[rev[i]]);
    for(rg int i=1;i<N;i<<=1)
    {
        int gn=power(G,(mod-1)/(i<<1));
        for(rg int j=0;j<N;j+=(i<<1))
        {
            int g=1;
            for(rg int k=0;k<i;k++,g=(ll)g*gn%mod)
            {
                int x=a[j+k],y=(ll)g*a[j+k+i]%mod;
                a[j+k]=pls(x,y);a[j+k+i]=dec(x,y);
            }
        }
    }
    if(f==-1)
    {
        int inv=power(N,mod-2);reverse(a+1,a+N);
        for(int i=0;i<N;i++) a[i]=(ll)a[i]*inv%mod;
    }
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    #endif
    read(n);
    init();
    while(N<(n<<1)) N<<=1,l++;
    for(rg int i=1;i<N;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(l-1));
    for(rg int i=0;i<n;i++)
    {
        f[i]=inv[i];
        if(i&1) f[i]=dec(0,f[i]);
        g[i]=(ll)power(i,n)*inv[i]%mod;
    }
    ntt(f,1);ntt(g,1);
    for(rg int i=0;i<N;i++) f[i]=(ll)f[i]*g[i]%mod;
    ntt(f,-1);
    for(rg int i=0;i<n;i++) ans=pls(ans,(ll)f[i]*b[i]%mod);
    ans=pls(ans,(ll)mi[n]*fac[n]%mod);
    printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值