典中典之第二类斯特林数

第二类斯特林数:将 \(n\) 个元素划分成 \(m\) 个集合,每个集合非空的方案数,记为 \(S(n,m)\)

\(n^2\) 递推公式:\(S(n,m)=S(n-1,m-1)+m\cdot S(n-1,m)\).

附代码:

s[0][0]=1;
for(int i=1;i<=n;++i)
    for(int j=1;j<=i;++j)
        s[i][j]=add(s[i-1][j-1],mul(s[i-1][j],j));

第二类斯特林数的卷积公式:

\[S(n,m)=\sum_{k=0}^m \frac{(-1)^k}{k!}\frac{(m-k)^n}{(m-k)!} \]

斯特林数转下降幂公式:

\[x^k=\sum_{i=0}^kS(k,i)x^{\underline i} \]

又已知一常见组合公式(证明展开即可):

\[\binom{n}{x} x^{\underline i}=\binom{n-i}{x-i}n^{\underline i} \]

将以上两式合并则有:

\[\binom{n}{x}x^k = \sum_{i=0}^k S(k,i)\binom{n-i}{x-i} n^{\underline i} \]

对于枚举 \(x\) 的题目,一般只需交换求和号后将后面的组合数用二项式定理合并成 \(n-i\) 次多项式即可,这样可以消掉 \(x\),将时间复杂度由 \(n\) 降到 \(k\)

例1:CF932E Team Work

\(n\) 个物品中挑任意个,挑 \(x\) 个的价值为 \(x^k\)。求所有方案的价值和。\(n\le 10^9, k\le 5000\).

即求

\[\sum_{x=0}^n \binom{n}{x} x^k \]

由前文式子可得

\[=\sum_{x=0}^n \sum_{i=0}^k S(k,i)\binom{n-i}{x-i} n^{\underline i} \]

交换求和号

\[=\sum_{i=0}^{\min(k,n)} S(k,i)n^{\underline i}\sum_{x=i}^{n} \binom{n-i}{x-i} \]

改为枚举 \(x-i\)

\[=\sum_{i=0}^{\min(k,n)} S(k,i)n^{\underline i}\sum_{x=0}^{n-i} \binom{n-i}{x} \]

后半部分求和用二项式定理合并,即为

\[=\sum_{i=0}^{\min(k,n)} S(k,i)n^{\underline i} 2^{n-i} \]

\(O(k^2)\) 预处理第二类斯特林数后 \(O(k)\) 求解即可。

#include<bits/stdc++.h>
using namespace std;
const int N=5005,Mod=1e9+7;
#define mul(x,y) (1ll*(x)*(y)%Mod)
inline int add(int x, int y)
{
    return (x+y>=Mod?x+y-Mod:x+y);
}
inline int po(int x, int y)
{
    int r=1;
    for(;y;y>>=1,x=mul(x,x)) if(y&1) r=mul(r,x);
    return r;
}
int s[N][N],n,m,k;
int main()
{
    int n,k; scanf("%d%d",&n,&k);
    s[0][0]=1;
    for(int i=1;i<=k;++i)
        for(int j=1;j<=i;++j)
            s[i][j]=add(s[i-1][j-1],mul(s[i-1][j],j));
    int ans=0;
    for(int i=0,b=1,c=po(2,n),iv=po(2,Mod-2);i<=min(n,k);++i)
    {
        int t=mul(s[k][i],mul(b,c));
        ans=add(ans,t);
        b=mul(b,n-i), c=mul(c,iv);
    }
    printf("%d\n",ans);
}

例2:CF1716F Bags with Balls

\(n\) 个盒子,每个盒子里放着编号 \(1\sim m\) 的球。每次从每个盒子里分别抽取 1 个球,令抽到的编号为奇数的球的个数为 \(x\),则价值为 \(x^k\)。求所有可能方案的价值之和。\(T\) 组数据,\(n,m<998244353, k\le 2000\).

即为求

\[\sum_{x=0}^n x^k \binom{n}{x} \lceil\frac{m}{2}\rceil^x \lfloor \frac{m}{2}\rfloor ^ {n-x} \]

由上文结论易推得

\[=\sum_{i=0}^{\min(k,n)} S(k,i)n^{\underline i}\sum_{x=0}^{n-i} \binom{n-i}{x} \lceil\frac{m}{2}\rceil^{x+i} \lfloor \frac{m}{2}\rfloor ^ {n-i-x} \]

提取一个 \(\lceil\frac{m}{2}\rceil^i\),后面用二项式定理合并,即为 \(m^{n-i}\)

\[=\sum_{i=0}^{\min(k,n)} S(k,i)n^{\underline i} \lceil\frac{m}{2}\rceil^i m^{n-i} \]

线性计算即可。复杂度 \(O(k^2+Tk)\)

#include<bits/stdc++.h>
using namespace std;
const int N=2022,Mod=998244353;
#define mul(x,y) (1ll*(x)*(y)%Mod)
inline int add(int x, int y)
{
    return (x+y>=Mod?x+y-Mod:x+y);
}
inline int po(int x, int y)
{
    int r=1;
    for(;y;y>>=1,x=mul(x,x)) if(y&1) r=mul(r,x);
    return r;
}
int s[N][N],n,m,k;
void Init()
{
    const int n=2000;
    s[0][0]=1;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=i;++j)
            s[i][j]=add(s[i-1][j-1],mul(s[i-1][j],j));
}
int main()
{
    Init();
    int T; scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%d",&n,&m,&k);
        const int o=(m-1)/2+1;
        int ans=0;
        for(int i=0,b=1,c=1,d=po(m,n),iv=po(m,Mod-2);i<=min(n,k);++i)
        {
            int t=mul(s[k][i],mul(mul(b,c),d));
            ans=add(ans,t);
            b=mul(b,n-i), c=mul(c,o), d=mul(d,iv);
        }
        printf("%d\n",ans);
    }
}

CF1287F 与本题基本一致,这里就不写了。

例3. LOJ3300 「联合省选 2020 A」组合数问题

计算

\[\left(\sum_{k=0}^n f(k) \times x^k \times \binom n k\right) \bmod p \]

的值。其中 \(n, x, p(\le 10^9)\) 为给定的整数,\(f(k)\) 为给定的一个 \(m(\le \min(n,1000))\) 次多项式 \(f(k) = a_0 + a_1 k + a_2 k^2 + \cdots + a_m k^m\)

将多项式展开

\[=\sum_{i=0}^m a_i \sum_{k=0}^n \binom{n}{k}k^ix^k \]

按前文变换易得

\[=\sum_{i=0}^m a_i \sum_{j=0}^i S(i,j)n^{\underline j} \sum_{k=j}^n \binom{n-j}{k-j}x^k\\ =\sum_{i=0}^m a_i \sum_{j=0}^i S(i,j)n^{\underline j} x^j \sum_{k=0}^{n-j} \binom{n-j}{k}x^k\\ =\sum_{i=0}^m a_i \sum_{j=0}^i S(i,j)n^{\underline j} x^j(x+1)^{n-j} \]

\(O(m^2)\) 预处理+计算即可。

#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int n,x,p,m,s[N][N],d[N];
#define mul(x,y) (1ll*(x)*(y)%p)
inline int add(int x, int y)
{
    return (x+y>=p?x+y-p:x+y);
}
inline int po(int x, int y)
{
    int r=1;
    for(;y;y>>=1,x=mul(x,x)) if(y&1) r=mul(r,x);
    return r;
}
int calc(int k)
{
    int res=0;
    d[k]=po(x+1,n-k);
    for(int i=k-1;~i;--i) d[i]=mul(d[i+1],x+1);
    for(int i=0,b=1,c=1;i<=min(n,k);++i)
    {
        int t=mul(s[k][i],mul(mul(b,c),d[i]));
        res=add(res,t);
        b=mul(b,n-i), c=mul(c,x);
    }
    return res;
}
int main()
{
    scanf("%d%d%d%d",&n,&x,&p,&m);
    s[0][0]=1;
    for(int i=1;i<=m;++i)
        for(int j=1;j<=i;++j)
            s[i][j]=add(s[i-1][j-1],mul(s[i-1][j],j));
    int ans=0;
    for(int i=0;i<=m;++i)
    {
        int a; scanf("%d",&a);
        ans=add(ans,mul(a,calc(i)));
    }
    printf("%d\n",ans);
}
posted @ 2022-08-11 00:46  x_faraway_x  阅读(307)  评论(2编辑  收藏  举报