两类斯特林数 (组合数学)

介绍

斯特林数是组合数学中的一个重要内容,有许多有用的性质.它由十八世纪的苏格兰数学家James Stirling首先发现并说明了它们的重要性.

斯特林数主要处理的是把N个不同的元素分成k个集合或环的个数问题.现在我们说的斯特林数可以指两类数,分为第一类斯特林数和第二类斯特林数,其中第一类斯特林数还分成有符号和无符号两种.

第一类斯特林数

Problem

将 n 个不同的元素全部放入 m 个环中,不允许有空环出现,求方案数.

定义:

\[s(i,j) \]

为 i 个人分成 j 组做环排列的方法数目.


对于每一个s( i , j )我们考虑第 i 个物品,有两种情况:

1. i 可以单独构成一个非空循环排列,这样前 i-1 种物品构成 j-1 个非空循环排列,方法数为s(i-1,j-1);

2.也可以前 i-1 种物品构成 j 个非空循环排列,而第 i 个物品插入第 i 个物品的左边,这有( i-1)*s( i-1 , j )种方法;

于是我们可以得到s(i,j)的递推公式:

\[s(i,j)=(i-1)*s(i-1,j)+s( i-1 , j-1 ) ,1<=i<=j-1 \]

边界条件:$$s(i,0)=0 ,p>=1$$
$$ s(i,i)=1 ,p>=0$$

时间复杂度为 O(n^2).

第二类斯特林数

Problem

将 n 个不同的元素全部放入 m 个集合中,不允许有空集出现,求方案数.

定义:$$S(n,m)$$
为把n个元素划分成m个无序集合的方案数.

1)递推式

根据这个定义我们不难写出递推式
设状态S(i,j),对于第i个元素我们则有两种情况:
1.若单独一个集合,则方案数等价于S(i-1,j-1).
2.若不是单独一个集合,则他可以在之前任意j个集合里,方案为j x S(i-1,j)


于是可以得到递推公式:

\[S(i,j)=S(i-1,j-1)+S(i-1,j)*j \]

边界条件:$$S(p,p)=1,(p>=0)$$
$$S(p,0)=0,(p>0)$$
时间复杂度为 O(n^2).

2)通项公式

假设集合没有非空的限制,则答案显然是m^n

之后我们可以利用容斥原理,枚举至少有几个集合是空的,在套上容斥系数我们有(注意集合是无序的,所以最后要消序)

\[S(n,m)=\frac{1}{m!}*\sum^{m}_{k=0} (-1)^k*C_m^k)*(m-k)^n) \]

直接根据这个式子暴力求解某一行的斯特林数的复杂度是O(n^2)的


这是一个卷积形式,利用FFT我们可以将时间复杂度优化成O(nlogn).

然后这里有一道 二类斯特林的裸题 .给出第二类斯特林数的递推部分代码,add 函数为高精度加法.(我不想打高精度,贴了别人的哈..)

void add(int u,int v)
{
    for(int i=1;i<=max(a[u-1][v-1][0],a[u-1][v][0]);i++)
    a[u][v][i]+=a[u-1][v-1][i],
     a[u][v][i]+=v*a[u-1][v][i];

    a[u][v][0]=max(a[u-1][v-1][0],a[u-1][v][0]);
    
    for(int i=1;i<=a[u][v][0];i++)
        a[u][v][i+1]+=a[u][v][i]/10,
        a[u][v][i]%=10;

    while(a[u][v][a[u][v][0]+1])
    {    
        a[u][v][0]++,
          a[u][v][a[u][v][0]+1]+=a[u][v][a[u][v][0]]/10;
            a[u][v][a[u][v][0]]%=10;
    }
}
{
    cin>>n>>m;
    a[1][1][0]=1;
    a[1][1][1]=1;
    for(int i=2;i<=n;i++)
    for(int j=1;j<=i;j++)
            add(i,j);
    if(a[n][m][0]==0)
    {cout<<"0"<<endl;return 0;}
    for(int i=a[n][m][0];i>=1;i--)
    cout<<a[n][m][i];
    return 0;
}
posted @ 2018-06-12 10:52  Kevin_naticl  阅读(493)  评论(0编辑  收藏  举报