组合数学之斯特林数 贝尔数

本文探讨了斯特林数在解决组合数学中的上升下降问题,特别是与环形排列和集合划分相关的应用。通过递推公式解释了第一类斯特林数如何计算不同元素构成环的方案数,并举例说明其在求解楼房排列问题中的应用。同时,介绍了第二类斯特林数,它涉及无序集合的拆分,与贝尔数的关系也在文中进行了解释。

斯特林数经常和组合数学中的上升下降问题联系到一起。

第一类斯特林数:将n个不同的元素构成m个不同的环的方案数目(两环不想等当且仅当任一不能通过旋转得到另一环)

dp[i][j]表示i个元素构成j个环

有两种情况可以得到dp[i][j]:

1.  前i-1个元素构成了j-1个不同的环,第i个元素单独成环  共有 dp[i-1][j-1]种情况

2.  前i-1个元素构成了j个不同的环,第i个元素可以任意插入任何一个环的任何一个位置(不考虑顺逆时针)共有 dp[i-1][j]*(i-1)中情况

得到第一类斯特林数的递推式:dp[i][j]=dp[i-1][j-1]+dp[i-1][j]*(i-1)

最后考虑边界情况: dp[i][i]=1  dp[i][0]=0;

例题: hdu-4372  vj传送门:https://vjudge.net/problem/HDU-4372

题目大意:给定n个楼房,高度从1~n,从左边往右边最多能同时看见a幢楼房,从右边往左看最多同时看见b幢楼房

问有多少种合法的排列方式。

很显然,是个组合数学的问题,然后还和LIS有关。。联想到斯特林数,接着分析题目。

从左或者从右是肯定都会看到最高的楼的,优先提出n,这样n左边的LIS=a-1  右边为 b-1

 而如果是集合划分问题的话,集合内部是无序的,很显然不是这样的,考虑环排序。

将n-1个数划分为a+b-2个环,每个环用环中最大的数作为起点(也就是每个环用其中最大的数表示)

 在a+b-2个环中选a-1个环 按照环中的最大值 升序排序,这样从左到右看过去每次只能看到每个环的最大值,恰好a-1个环就是LIS为a-1,右边降序同理。

这样合法的方案数就为: sum=S1[n-1][a+b-2]*C[a+b-2][a-1];

#include <iostream>
const long long mod=1e9+7;
#define ll long long
ll s[2010][2010];
ll c[2010][2010];
void init()
{
    c[0][0] = 1;
    s[0][0] = 1;
    for(int i = 1; i < 2005; i++)
    {
        c[i][0] = c[i][i] = 1;
        s[i][0] = 0;s[i][i] = 1;
        
        for(int j = 1; j < i; j++)
        {
            c[i][j] = (c[i-1][j] + c[i-1][j-1])%mod;
            s[i][j] = (s[i-1][j]*(i-1)+s[i-1][j-1])%mod;
        }
    }

}

using namespace std;
int main()
{
    init();
    //cout<<c[5][2]<<endl;
    int t;
    cin>>t;
    while(t--)
    {
        int n,a,b;
        cin>>n>>a>>b;
        if(a+b-2>n)
        {
            cout<<0<<endl;
            continue;
        }
        else 
        {
            long long ans = (s[n - 1][a + b - 2] % mod * c[a + b - 2][a - 1] % mod) % mod;
            cout << ans << endl;

        }
    }

    return 0;
}

 

 

第二类斯特林数:实质上是集合的拆分,表示将n个不同的元素拆分成m个集合的方案数

和第一类不同,集合是没有顺序的,所以第二种情况的时候不是*(i-1) 而是乘上当前的集合数量,即*j

同理可得:第二类斯特林数的递推式:  dp[i][j]=dp[i-1][j-1]+dp[i-1][j]*j 

hdu-2512  vj传送门https://vjudge.net/problem/HDU-2512

有n张不同的卡,问有多少种集合划分的方式

裸的第二类斯特林数:

#include <iostream>
#include <cstring>
using namespace std;
const int mod=1000;
int dp[2005][2005];//dp[i][j]表示共有i张卡,分成j堆的情况数
int sum[2005];
void init()
{
    memset(dp,0,sizeof(dp));
    memset(sum,0,sizeof(sum));
    //dp[i][j]可以从两种情况推出:
    //对于第i张卡,如果前i-1张卡已经分为j堆,那么第i张卡可以任意放入任何一堆中,即有dp[i-1][j]*(j)种放法
    //如果前i张卡分为了j-1堆,那么第i张卡只能自己形成一个新的堆,即有dp[i-1][j-1]*1种放法
    for(int i=1;i<2005;i++)
    {
        dp[i][1]=1;dp[i][i]=1;//i张卡分成1堆和i堆只有一种情况
        for(int j=2;j<i;j++)
        {
            dp[i][j]=(dp[i-1][j-1]+dp[i-1][j]*j)%mod;
        }
    }
    for(int i=1;i<2005;i++)
    {
        for(int j=1;j<=i;j++)
        {
            sum[i]=(sum[i]+dp[i][j])%mod;
        }
    }
}


int main()
{
    init();
    int n;
    int t;
    cin>>t;
    while(t--)
    {
        cin>>n;
        cout<<sum[n]<<endl;
    }



    return 0;
}

这里的sum[i] 就是贝尔数,其意义为有i个不相同的元素,划分集合的方案数目

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值