【BZOJ4555】求和(多种解法混合版本)

232 篇文章 0 订阅
33 篇文章 0 订阅

题面

BZOJ
给定 n n ,求

f(n)=i=0nj=0iS(i,j)×2j×(j!)

n<=100000 n <= 100000 ,结果对 998244353 998244353 取模。

其中 S(i,j) S ( i , j ) 是第二类斯特林数,表示将 i i 个有区别的球放入j个相同的盒子中,每个盒子非空的方案数。

S(n,m)=S(n1,m1)+S(n1,m)m S ( n , m ) = S ( n − 1 , m − 1 ) + S ( n − 1 , m ) ∗ m

边界条件: S(i,0)=0,S(i,i)=1 S ( i , 0 ) = 0 , S ( i , i ) = 1

题解

第二类斯特林数考虑组合意义,可以得到另外一个式子

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

具体的考虑方法是:先假设盒子不同,然后枚举哪些盒子为空,容斥算出所有盒子都非空的方案数,然后除掉盒子不同产生的多余的贡献。

将组合数拆开,我们得到

S(n,m)=1m!i=0m(1)im !i !(mi)!(mi)n S ( n , m ) = 1 m ! ∑ i = 0 m ( − 1 ) i m   ! i   ! ( m − i ) ! ( m − i ) n

S(n,m)=i=0m(1)ii !(mi)n(mi)! S ( n , m ) = ∑ i = 0 m ( − 1 ) i i   ! ( m − i ) n ( m − i ) !

这很明显是一个卷积的形式,因此,我们可以利用 FFT/NTT F F T / N T T O(nlogn) O ( n l o g n ) 的时间内预处理所有的 S(n,i),i[0,n] S ( n , i ) , i ∈ [ 0 , n ]

做法一:第二类斯特林数展开式(容斥原理)

f(n)=i=0nj=0nS(i,j)2jj! f ( n ) = ∑ i = 0 n ∑ j = 0 n S ( i , j ) 2 j j !

将第二类斯特林数展开为卷积的形式
=i=0nj=0nj!·2j(1j!k=0j(1)k·Ckj·(jk)i) = ∑ i = 0 n ∑ j = 0 n j ! · 2 j ( 1 j ! ∑ k = 0 j ( − 1 ) k · C j k · ( j − k ) i )

j=0n2jk=0j(1)k·Ckj·i=0n(jk)i ∑ j = 0 n 2 j ∑ k = 0 j ( − 1 ) k · C j k · ∑ i = 0 n ( j − k ) i

j=0n2j·j!k=0j(1)kk!ni=0(jk)i(jk)! ∑ j = 0 n 2 j · j ! ∑ k = 0 j ( − 1 ) k k ! ∑ i = 0 n ( j − k ) i ( j − k ) !

j=0n2j·j!k=0j(1)kk!(jk)n+11(jk)!(jk1) ∑ j = 0 n 2 j · j ! ∑ k = 0 j ( − 1 ) k k ! ( j − k ) n + 1 − 1 ( j − k ) ! ( j − k − 1 )

不难观察到后半部分是卷积的形式, NTT N T T 预处理即可。
时间复杂度 O(nlogn) O ( n l o g n )


定义 Bell B e l l B(n)=ni=1S(n,i) B ( n ) = ∑ i = 1 n S ( n , i ) 。表示 i i 个不同的球分成任意个非空集合的方案数。
我们可以得到式子

B(n)=i=0n1Cn1iB(i)

证明:考虑组合意义,枚举与 1 1 号球在不同集合内的个数,相当于划分为了子问题处理。
S(n,i)=S(n,i)i !,表示集合不相同的第二类斯特林数。同理定义 B(n) B ′ ( n )

B(n)=i=1nCinB(ni) B ′ ( n ) = ∑ i = 1 n C n i B ′ ( n − i )

证明:集合之间已经没有关系了,枚举第一个集合中的元素个数,其他的相当于划分为了子问题处理。
设题目所求
i=0nj=0iS(i,j)×2j×(j!)=i=0nf(i) ∑ i = 0 n ∑ j = 0 i S ( i , j ) × 2 j × ( j ! ) = ∑ i = 0 n f ( i )

f(n)=i=0nS(i,j)j !2j f ( n ) = ∑ i = 0 n S ( i , j ) j   ! 2 j

前面部分相当于 B(n) B ′ ( n ) ,后面部分可以看做每一个集合有两种颜色,每次在一种颜色中放球。
那么,类似于前面的 S,B S ′ , B ′ 我们可以写出
f(n)=2i=1nCinf(ni) f ( n ) = 2 ∑ i = 1 n C n i f ( n − i )

即考虑第一个集合中的元素个数,以及当前集合的颜色。

将组合数拆开考虑,得到。

f(n)=2n!i=1n1i !f(ni)(ni)! f ( n ) = 2 n ! ∑ i = 1 n 1 i   ! f ( n − i ) ( n − i ) !

方法二: CDQ C D Q 分治 +FFT + F F T /分治 FFT F F T

将式子稍微改写一下

f(n)n!=i=0n12i !f(i)(ni)! f ( n ) n ! = ∑ i = 0 n − 1 2 i   ! f ( i ) ( n − i ) !

g(i)=2i ! g ( i ) = 2 i   ! 。可以使用 CDQ C D Q 分治+ FFT F F T 来实现。

我们观察到如果要算出 f(n) f ( n ) , f(1..n1) f ( 1.. n − 1 ) 就必须求得,于是,我们进行分治,每次左侧的 f f 值可以对右侧的值产生贡献,贡献就是f(i) g(i) g ( i ) 的卷积。这就是一个 CDQ C D Q 分治,时间复杂度 O(nlog2n) O ( n l o g 2 n )

方法三:生成函数+多项式求逆

f(n)n!=i=0n12i !f(i)(ni)! f ( n ) n ! = ∑ i = 0 n − 1 2 i   ! f ( i ) ( n − i ) !

F(x) F ( x ) f(n) f ( n ) EGF E G F , G(n) G ( n ) g(i)=2i ! g ( i ) = 2 i   ! OEG O E G ,特别注意的是, g(0)=0 g ( 0 ) = 0

所以我们有 F(x)=F(x)G(x)+1 F ( x ) = F ( x ) G ( x ) + 1 。需要 +1 + 1 的原因是 g(0)=0,f(0)=1 g ( 0 ) = 0 , f ( 0 ) = 1 。于是我们解一下方程

F(x)=11G(x) F ( x ) = 1 1 − G ( x )

多项式求逆即可。
时间复杂度 O(nlogn) O ( n l o g n )


方法一代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
#define MOD 998244353
#define MAX 500000
const int pr=3;
const int phi=MOD-1;
int fpow(int a,int b)
{
    int s=1;
    while(b){if(b&1)s=1ll*s*a%MOD;a=1ll*a*a%MOD;b>>=1;}
    return s;
}
int r[MAX],N,M,l;
int jc[MAX],inv[MAX];
int a[MAX],b[MAX],S[MAX];
void NTT(int *P,int opt)
{
    for(int i=0;i<N;++i)if(i<r[i])swap(P[i],P[r[i]]);
    for(int i=1;i<N;i<<=1)
    {
        int W=fpow(pr,phi/(i<<1));
        for(int p=i<<1,j=0;j<N;j+=p)
        {
            int w=1;
            for(int k=0;k<i;++k,w=1ll*w*W%MOD)
            {
                int X=P[j+k],Y=1ll*w*P[i+j+k]%MOD;
                P[j+k]=(X+Y)%MOD;P[i+j+k]=((X-Y)%MOD+MOD)%MOD;
            }
        }
    }
    if(opt==-1)reverse(&P[1],&P[N]);
}
void Work()
{
    M+=N;
    for(N=1;N<=M;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]=1ll*a[i]*b[i]%MOD;
    NTT(a,-1);
    for(int i=0,inv=fpow(N,MOD-2);i<N;++i)a[i]=1ll*a[i]*inv%MOD;
}
int n,ans;
int main()
{
    scanf("%d",&n);
    N=M=n;
    jc[0]=inv[0]=1;
    for(int i=1;i<=n;++i)jc[i]=1ll*jc[i-1]*i%MOD;
    for(int i=1;i<=n;++i)inv[i]=fpow(jc[i],MOD-2);
    for(int i=0;i<=n;++i)a[i]=(i&1)?-1:1;
    for(int i=0;i<=n;++i)a[i]=(1ll*a[i]*inv[i]%MOD+MOD)%MOD;
    for(int i=2;i<=n;++i)b[i]=(fpow(i,n+1)-1+MOD)%MOD;
    for(int i=2;i<=n;++i)b[i]=1ll*b[i]*inv[i]%MOD;
    for(int i=2;i<=n;++i)b[i]=1ll*b[i]*fpow(i-1,MOD-2)%MOD;
    b[0]=1;b[1]=n+1;
    Work();
    for(int i=0,p=1;i<=n;++i,p=(p+p)%MOD)ans=(ans+1ll*jc[i]*p%MOD*a[i]%MOD)%MOD;
    printf("%d\n",ans);
    return 0;
}

方法二代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
#define MAX 333333
#define MOD (998244353)
inline int read()
{
    RG int x=0,t=1;RG char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
int fpow(int a,int b)
{
    int s=1;
    while(b){if(b&1)s=1ll*s*a%MOD;a=1ll*a*a%MOD;b>>=1;}
    return s;
}
int jc[MAX],inv[MAX],jv[MAX],n;
int f[MAX],g[MAX];
void prepare(int N)
{
    jc[0]=jv[0]=jc[1]=inv[1]=jv[1]=1;
    for(int i=2;i<=N;++i)jc[i]=1ll*jc[i-1]*i%MOD;
    for(int i=2;i<=N;++i)inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
    for(int i=2;i<=N;++i)jv[i]=1ll*jv[i-1]*inv[i]%MOD;
    for(int i=1;i<=N;++i)g[i]=(jv[i]+jv[i])%MOD;
}
int r[MAX];
void NTT(int *P,int len,int opt)
{
    int N,l=0;for(N=1;N<len;N<<=1)++l;
    for(int i=0;i<N;++i)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
    for(int i=0;i<N;++i)if(i<r[i])swap(P[i],P[r[i]]);
    for(int i=1;i<N;i<<=1)
    {
        int W=fpow(3,(MOD-1)/(i<<1));
        for(int p=i<<1,j=0;j<N;j+=p)
        {
            int w=1;
            for(int k=0;k<i;++k,w=1ll*w*W%MOD)
            {
                int X=P[j+k],Y=1ll*w*P[i+j+k]%MOD;
                P[j+k]=(X+Y)%MOD;P[i+j+k]=(X+MOD-Y)%MOD;
            }
        }
    }
    if(opt==-1)
    {
        reverse(&P[1],&P[N]);
        for(int i=0,inv=fpow(N,MOD-2);i<N;++i)P[i]=1ll*P[i]*inv%MOD;
    }
}
int A[MAX],B[MAX];
void CDQ(int l,int r)
{
    if(l==r)return;
    int mid=(l+r)>>1;
    CDQ(l,mid);
    for(int i=0;i<=mid-l;++i)A[i]=f[i+l];
    for(int i=0;i<=r-l;++i)B[i]=g[i];
    int N;for(N=1;N<=mid+r-l-l+1;N<<=1);
    NTT(A,N,1);NTT(B,N,1);
    for(int i=0;i<N;++i)A[i]=1ll*A[i]*B[i]%MOD;
    NTT(A,N,-1);
    for(int i=mid-l+1;i<=r-l;++i)f[i+l]=(A[i]+f[i+l])%MOD;
    for(int i=0;i<N;++i)A[i]=B[i]=0;
    CDQ(mid+1,r);
}
int main()
{
    prepare(n=read());
    f[0]=1;
    CDQ(0,n);
    int ans=0;
    for(int i=0;i<=n;++i)ans=(ans+1ll*f[i]*jc[i]%MOD)%MOD;
    printf("%d\n",ans);
    return 0;
}

方法三代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
#define MAX 333333
#define MOD (998244353)
inline int read()
{
    RG int x=0,t=1;RG char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
int fpow(int a,int b)
{
    int s=1;
    while(b){if(b&1)s=1ll*s*a%MOD;a=1ll*a*a%MOD;b>>=1;}
    return s;
}
int inv[MAX],jc[MAX],n;
int f[MAX],g[MAX];
void prepare(int N)
{
    inv[1]=inv[0]=jc[0]=jc[1]=1;
    for(RG int i=2;i<=N;++i)jc[i]=1ll*jc[i-1]*i%MOD;
    for(RG int i=2;i<=N;++i)inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
    for(RG int i=2;i<=N;++i)inv[i]=1ll*inv[i]*inv[i-1]%MOD;
    for(RG int i=1;i<=N;++i)g[i]=(inv[i]+inv[i])%MOD;
}
int r[MAX],W[MAX];
void NTT(int *P,int len,int opt)
{
    int N,l=0;for(N=1;N<len;N<<=1)++l;
    for(RG int i=0;i<N;++i)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
    for(RG int i=0;i<N;++i)if(i<r[i])swap(P[i],P[r[i]]);
    for(RG int i=1;i<N;i<<=1)
    {
        RG int w=fpow(3,(MOD-1)/(i<<1));
        W[0]=1;for(RG int k=1;k<i;++k)W[k]=1ll*W[k-1]*w%MOD;
        for(RG int p=i<<1,j=0;j<N;j+=p)
            for(RG int k=0;k<i;++k)
            {
                int X=P[j+k],Y=1ll*W[k]*P[i+j+k]%MOD;
                P[j+k]=(X+Y)%MOD;P[i+j+k]=(X+MOD-Y)%MOD;
            }
    }
    if(opt==-1)
    {
        reverse(&P[1],&P[N]);
        for(RG int i=0,inv=fpow(N,MOD-2);i<N;++i)P[i]=1ll*P[i]*inv%MOD;
    }
}
int A[MAX],B[MAX];
void Inv(int *a,int *b,int len)
{
    if(len==1){b[0]=fpow(a[0],MOD-2);return;}
    Inv(a,b,len>>1);
    for(RG int i=0;i<len;++i)A[i]=a[i],B[i]=b[i];
    NTT(A,len<<1,1);NTT(B,len<<1,1);
    for(RG int i=0;i<(len<<1);++i)B[i]=1ll*A[i]*B[i]%MOD*B[i]%MOD;
    NTT(B,len<<1,-1);
    for(RG int i=0;i<len;++i)b[i]=((b[i]+b[i])%MOD+MOD-B[i])%MOD;
    for(RG int i=0;i<(len<<1);++i)A[i]=B[i]=0;
}
int main()
{
    prepare(n=read());
    int N;for(N=1;N<=n;N<<=1);
    for(RG int i=0;i<N;++i)g[i]=(MOD-g[i])%MOD;g[0]=1;
    Inv(g,f,N);
    int ans=0;
    for(RG int i=0;i<=n;++i)ans=(ans+1ll*f[i]*jc[i]%MOD)%MOD;
    printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值