[HEOI2016/TJOI2016]求和

本文详细解析了如何利用第二类斯特林数和卷积技巧解决复杂的数学求和问题,通过深入理解斯特林数的组合意义及容斥原理,最终转化为多项式卷积形式并给出高效算法实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

\[\sum_{i=0}^n\sum_{j=0}^iS_2(i,j)2^jj!\]

\(S_2(i,j)\)为第二类斯特林数

这什么神仙操作,这种东西能求?

慢慢来做

首先想到二类斯特林的组合意义,就是把\(i\)个球放到\(j\)个盒子里且不允许有空

我们容斥一波,发现

\[S_2(i,j)=\frac{1}{j!}\sum_{k=0}(-1)^k\binom{j}{k}(j-k)^i\]

这个的意思就是强行枚举有\(k\)个盒子什么都不能选,这样就是组合数\(\binom{j}{k}\),之后剩下的\(i\)个小球每个都会有\(j-k\)种选择,就是\((j-k)^i\),这样算出来的是至少有\(k\)个空盒子的方案,我们容斥一下就有了那个容斥系数

至于为什么容斥系数是\((-1)^k\)

让我们来证明一下

上面那个柿子的含义显然是至少有\(k\)个空盒子,那么我们来考虑一下如果有\(n\)个空盒子会被算到多少次

显然是

\[\sum_{i=0}^n\binom{n}{i}(-1)^i\]

显然在\(n>=1\)的情况下我们可以直接使用二项式定理把这个柿子改写成\((1-1)^n=0\),在\(n=0\)的时候这个柿子的值却是\(1\)

但是我们组合数是使盒子产生了差别,于是外面除以\(j!\)消除这个差别

还发现\(j>i\)的时候,\(S_2(i,j)=0\),我们可以考虑把\(j\)的上标改写成\(n\)

于是我们可以把柿子写成

\[\sum_{i=0}^n\sum_{j=0}^n\sum_{k=0}^j\frac{1}{j!}(-1)^k\binom{j}{k}(j-k)^i2^jj!\]

我们优先展开组合数

\[\sum_{i=0}^n\sum_{j=0}^n2^jj!\sum_{k=0}^j\frac{(-1)^kj!(j-k)^i}{j!(j-k)!k!}\]

发现分子分母都有\(j!\),于是约去

\[\sum_{i=0}^n\sum_{j=0}^n2^jj!\sum_{k=0}^j\frac{(-1)^k}{k!}\times \frac{(j-k)^i}{(j-k)!}\]

发现里面开始有点像卷积的形式了,开始喜闻乐见交换求和符号

\[\sum_{j=0}^n2^jj!\sum_{k=0}^j\frac{(-1)^k}{k!}\sum_{i=0}^n\frac{(j-k)^i}{(j-k)!}\]

\(k+j-k\)可是等于\(j\)的我们终于看到卷积形式了

于是搞两个多项式

\[F_i=\frac{(-1)^i}{i!},G_i=\frac{\sum_{j=0}^ni^j}{i!}=\frac{\frac{i^{n+1}-1}{i-1}}{i!}=\frac{i^{n+1}-1}{i!(i-1)}\]

这两个多项式一卷就是答案了

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define re register
#define LL long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
const int maxn=262144+10005;
inline int read() {
    char c=getchar();int x=0;while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
int n,len,rev[maxn];
LL inv[maxn],fac[maxn],A[maxn],B[maxn];
const LL G[2]={3,332748118};
const LL mod=998244353;
inline LL quick(LL a,LL b) {LL S=1;while(b) {if(b&1) S=S*a%mod;b>>=1;a=a*a%mod;}return S;}
inline void NTT(LL *f,int o) {
    for(re int i=0;i<len;i++) if(i<rev[i]) std::swap(f[i],f[rev[i]]);
    for(re int i=2;i<=len;i<<=1) {
        int ln=i>>1;LL og1=quick(G[o],(mod-1)/i);
        for(re int l=0;l<len;l+=i) {
            LL t,og=1;
            for(re int x=l;x<l+ln;x++) {
                t=(f[ln+x]*og)%mod;
                f[ln+x]=(f[x]-t+mod)%mod;
                f[x]=(f[x]+t)%mod;
                og=(og*og1)%mod;
            }
        }
    }
    if(!o) return;
    LL Inv=quick(len,mod-2);
    for(re int i=0;i<len;i++) f[i]=(f[i]*Inv)%mod; 
}
int main() {
    n=read();
    fac[0]=1;
    for(re int i=1;i<=n;i++) fac[i]=(fac[i-1]*(LL)i)%mod;
    inv[n]=quick(fac[n],mod-2);
    for(re int i=n-1;i>=0;--i) inv[i]=(inv[i+1]*(LL)(i+1))%mod;
    for(re int i=0;i<=n;i++) if(i&1) A[i]=mod-inv[i];else A[i]=inv[i];
    B[0]=1,B[1]=n+1;
    for(re int i=2;i<=n;i++) 
        B[i]=(quick(i,n+1)-1)*inv[i]%mod*quick(i-1,mod-2);
    len=1;
    while(len<n+n) len<<=1;
    for(re int i=0;i<len;i++) rev[i]=(rev[i>>1]>>1)|((i&1)?len>>1:0);
    NTT(A,0),NTT(B,0);
    for(re int i=0;i<len;i++) A[i]=(A[i]*B[i])%mod;
    NTT(A,1);
    LL ans=0,pow=1;
    for(re int i=0;i<=n;i++) {
        ans+=A[i]*fac[i]%mod*pow%mod;
        ans%=mod,pow=(pow*2ll)%mod;
    }
    printf("%lld\n",ans);
    return 0;
}

转载于:https://www.cnblogs.com/asuldb/p/10544302.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值