📜  门| GATE CS 2019 |第 36 题(1)

📅  最后修改于: 2023-12-03 15:42:11.979000             🧑  作者: Mango

门| GATE CS 2019 |第 36 题

这是一道 GATE CS 2019 的题目,考察了应试者对于组合数学和递归算法的掌握程度。

题目描述

假设有 $n$ 个人排成一列,其中每个人的编号从 $1$ 到 $n$。现在将这些人分成若干个区域,每个区域中至少有一个人。同时,为了避免混淆,不能将两个相邻的人分到同一个区域中。对于 $n=3$,可以有以下三种情况:

  1. {1, 2}, {3}
  2. {1, 3}, {2}
  3. {2, 3}, {1}

现在,你的任务是按照上述要求,计算出将 $n$ 个人分成若干个区域的方案数。

输入格式

输入一个整数 $n$,表示排队的人数。

输出格式

输出分组的方案数,注意答案需要对 $10^9 + 7$ 取模。

示例

输入

3

输出

2
解题思路

这道题目涉及到了组合数学中的划分数问题。假如将 $n$ 个人划分成 $m$ 个区域,那么这个问题可以理解成将其编号放在 $m-1$ 个槽中,使得每个槽都至少有一个编号,所以方案数就是 $S(n,m-1)$,也就是第一类斯特林数。

对于 $n$ 个编号的人,若要将它们划分成若干个区域,可以首先将第 $n$ 个人与它前面的人分到不同的区域中,个数为 $f(n)$。这时,剩下的 $n-1$ 个编号可以分成 $m$ 个区域,方案数为 $S(n-1,m)$。因此,$n$ 个编号分成 $m$ 个区域的方案数为:

$$ f(n,m)=\sum_{k=1}^{n}f(n-k)S(n-1,k-1) $$

同时,可以根据上述公式建立递归的实现。

代码实现
MOD = 1000000007

def get_stirling_numbers(n: int, m: int) -> int:
    if m == 0:
        return 0
    if n == 0:
        return 1
    dp = [[0] * (n + 1) for _ in range(m)]
    for i in range(n + 1):
        dp[0][i] = 1
    for i in range(1, m):
        for j in range(1, n + 1):
            dp[i][j] = (dp[i - 1][j - 1] + (i-1) * dp[i - 1][j]) % MOD
    return dp[m - 1][n]

def get_dividing_method_count(n: int) -> int:
    f = [0] * (n + 1)
    f[1] = 1
    for i in range(2, n + 1):
        f[i] = sum(f[j] * get_stirling_numbers(i - 1, j) % MOD for j in range(1, i))
    return f[n] % MOD

# 测试样例
n = 3
print(get_dividing_method_count(n)) # 2

代码中写了两个函数,其中 get_stirling_numbers 实现了第一类斯特林数的递推式,而 get_dividing_method_count 则利用上述公式和递推式完成了整个计算的过程。

总结

这道题目的解法比较妙,在考察递归应用的同时,也让应试者对组合数学的一些基本概念和相关算法有了更深刻的认识。