斯特林(Stirling)数

斯特林(Stirling)数

斯特林数是组合数学中,关于子集划分和置换划分的一类问题的解。其中,分为第一类斯特林数和第二类斯特林数。

第一类斯特林数

[ n k ] {n \brack k} [kn] 表示 n n n个元素排成 k k k个轮换。这里的轮换是指:

例如存在轮换 [ 1 , 2 , 3 ] [1,2,3] [1,2,3],那么意思就是存在三个相等序列 [ 1 , 2 , 3 ] = [ 2 , 3 , 1 ] = [ 3 , 1 , 2 ] [1,2,3]=[2,3,1]=[3,1,2] [1,2,3]=[2,3,1]=[3,1,2]

特别的,一个元素的轮换只有他本身,因为 [ 1 ] [1] [1]单个元素只能和自己轮换。两个元素的轮换也只有一个,因为 [ 1 , 2 ] = [ 2 , 1 ] [1,2]=[2,1] [1,2]=[2,1]是同一个轮换。

一般来说,对于 [ n 1 ] {n \brack 1} [1n],每一个 n n n个元素的全排列正好对应 n n n个轮换,因此根据除法原理:

[ n 1 ] = n ! n = ( n − 1 ) ! {n \brack 1} = \frac{n!}{n} = (n-1)! [1n]=nn!=(n1)!

事实上,还容易看出:

[ n n ] = 1 {n \brack n} = 1 [nn]=1

[ n n − 1 ] = ( n 2 ) {n \brack n-1} = \binom{n}{2} [n1n]=(2n)

对于最后一个等式,运用的鸽巢原理,相当于我们把 n n n个元素放进 n − 1 n-1 n1个箱子里面,并且不能放空,那么必有两个元素在同一个箱子里面,因为有两个元素的那个箱子的轮换相等,所以我们不用做任何处理。

现在我们考虑第一类斯特林数的递推公式,考虑 n n n个元素最后一个元素 n n n,对于他的安放方式有两种。第一种自己单独成为一个轮换,方案数为 [ n − 1 k − 1 ] {n-1 \brack k-1} [k1n1]。第二种,插入前 n − 1 n-1 n1个元素生成的 k k k个轮换中,方案数为 ( n − 1 ) [ n − 1 k ] (n-1){n-1 \brack k} (n1)[kn1](对于每一个轮换,假如这个轮换的大小为 ∣ S ∣ |S| S,那么插入一个元素变成一个新的轮换有 ∣ S ∣ |S| S种方式,即可以在每一个元素前面插入而不重复,因为 ∑ ∣ S i ∣ = ( n − 1 ) \sum |S_i|=(n-1) Si=(n1),故总共有 ( n − 1 ) (n-1) (n1)种插入方式),因此,我们的递推公式为:

[ n k ] = [ n − 1 k − 1 ] + ( n − 1 ) [ n − 1 k ] {n \brack k} = {n-1 \brack k-1} + (n-1){n-1 \brack k} [kn]=[k1n1]+(n1)[kn1]

其中,我们认为:

[ 0 0 ] = 1 [ n 0 ] = 0 ( n > 0 ) {0 \brack 0} = 1 \\ {n \brack 0} = 0 (n \gt 0) [00]=1[0n]=0(n>0)

例题

看不到的火柴数

LeetCode 5762

设答案为 P ( n , k ) P(n,k) P(n,k)

我们考虑最后一个位置能看到或者不能看到。如果能看到那么长度为 n n n的火柴必定在最后一个位置,方案数为 P ( n − 1 , k − 1 ) P(n - 1,k - 1) P(n1,k1)。如果看不到,那么最后一个位置上的元素可以是前 [ 1 , n − 1 ] [1,n-1] [1,n1]内的所有元素和元素 n n n做置换,相对大小不变,因此方案数为 ( n − 1 ) ∗ P ( n − 1 , k ) (n-1)*P(n-1,k) (n1)P(n1,k)。因此递推公式为:

P ( n , k ) = P ( n − 1 , k − 1 ) + ( n − 1 ) ∗ P ( n − 1 , k ) P(n,k) = P(n - 1,k - 1) + (n-1)*P(n-1,k) P(n,k)=P(n1,k1)+(n1)P(n1,k)

答案就是第一类斯特林数。 P ( n , k ) = [ n k ] P(n,k) = {n \brack k} P(n,k)=[kn]

高楼

HDU4372

我们枚举最高建筑 n n n的位置,他前面可以有 [ 0 , n − 1 ] [0,n-1] [0,n1]个建筑,故答案为:

a n s = ∑ i = 0 n − 1 [ i f − 1 ] [ n − i − 1 b − 1 ] ( n − 1 i ) ans = \sum_{i = 0}^{n-1} {i \brack f - 1} {n - i - 1 \brack b - 1} \binom{n - 1}{i} ans=i=0n1[f1i][b1ni1](in1)

查询次数很多,这么做必定会超时。因此我们必须使用一个神奇的公式化简这个和式。

具体的,我们把最高建筑 n n n放在最后面,构造 [ n − 1 f + b − 2 ] {n - 1 \brack f + b - 2} [f+b2n1],接着,然后把前面的 b − 1 b-1 b1个圆排列移到后面去,因此答案为:

a n s = [ n − 1 f + b − 2 ] ( f + b − 2 b − 1 ) ans = {n - 1 \brack f + b - 2} \binom{f + b - 2}{b - 1} ans=[f+b2n1](b1f+b2)

这样,我们得到了一个很重要的公式:

[ n l + m ] ( l + m l ) = ∑ k [ k l ] [ n − k m ] ( n k ) {n \brack l + m} \binom{l + m}{l} = \sum_k {k \brack l}{n-k \brack m} \binom{n}{k} [l+mn](ll+m)=k[lk][mnk](kn)

第二类斯特林数

第二类斯特林数 { n m } {n \brace m} {mn}是指,将 n n n个不同的元素划分为 m m m个相同的非空集合的方案数。

有如下递推式子:

{ n m } = { n − 1 m − 1 } + { n − 1 m } × m {n \brace m} = {n-1 \brace m-1} + {n-1 \brace m} \times m {mn}={m1n1}+{mn1}×m

我们考虑最后一个元素的划分,第一种方式将最后一个元素单独放在一个集合中,需要将前 n − 1 n-1 n1个元素划分成 m − 1 m-1 m1个集合。第二种情况将最后一个元素插入到之前已经划分好的集合中,有 m m m中插入方式,插入到每个集合都是不同的,方案数为 { n − 1 m } × m {n-1 \brace m} \times m {mn1}×m

P1655 模板

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

#define FR freopen("in.txt", "r", stdin)
#define FW freopen("out.txt", "w", stdout)

struct Decimal
{
    int bits[500];
    int tot;
    Decimal() : Decimal(0)
    {
    }
    Decimal(int init) : tot(0)
    {
        fill(bits, bits + 500, 0);
        if (init == 0)
        {
            tot = 1;
            return;
        }
        for (; init; init /= 10)
        {
            bits[tot++] = init % 10;
        }
    }

    Decimal operator+(Decimal o)
    {
        Decimal ans(0);
        int i = 0;
        for (; i < max(tot, o.tot); i++)
        {
            ans.bits[i] += bits[i] + o.bits[i];
            ans.bits[i + 1] += ans.bits[i] / 10;
            ans.bits[i] %= 10;
        }
        ans.tot = ans.bits[i] == 0 ? i : i + 1;
        return ans;
    }

    Decimal operator*(Decimal o)
    {
        Decimal ans(0);
        for (int i = 0; i < tot; i++)
        {
            for (int j = 0; j < o.tot; j++)
            {
                ans.bits[i + j] += bits[i] * o.bits[j];
                ans.bits[i + j + 1] += ans.bits[i + j] / 10;
                ans.bits[i + j] %= 10;
            }
        }
        int t = tot + o.tot - 1;
        while (ans.bits[t] == 0)
            t--;
        ans.tot = t + 1;
        return ans;
    }

    Decimal operator*(int k)
    {
        Decimal fact(k);
        return (*this) * fact;
    }

    void output()
    {
        for (int i = tot - 1; i >= 0; i--)
        {
            putchar(bits[i] + '0');
        }
        putchar('\n');
    }
};

Decimal stir[105][105];

int main()
{
    stir[0][0] = Decimal(1);
    for (int n = 1; n <= 101; n++)
    {
        for (int m = 1; m <= min(101, n); m++)
        {
            stir[n][m] = stir[n - 1][m - 1] + stir[n - 1][m] * m;
        }
    }
    int N, M;
    while (scanf("%d %d", &N, &M) != EOF)
    {
        stir[N][M].output();
    }
    return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值