[递归]子集合划分

子集合划分

题目描述

将一个没有重复元素的集合A分成若干个非空子集,使得A中每个元素属于且仅属于一个子集,那么这些子集构成的集合称为A的一个划分。现在给定集合的元素个数n,希望知道有多少种不同的划分(假定集合本身属于一种划分),当n=0的时候,也认为有一种划分。
例如,n=3 时,A={1,2,3}的所有划分如下:
{ {1} , {2} , {3} }
{ {1 , 2} , {3} }
{ {1 , 3} , {2} }
{ {1} , {2 , 3} }
{ {1 , 2 , 3} }
一共有5种。

关于输入

若干行整数,每行一个整数n,当为-1时结束

关于输出

每行对应的集合划分数(假设可以用long表示)

例子输入

0
3
13
-1

例子输出

1
5
27644437

解题分析

这个程序的主要目标是计算 Bell 数,Bell 数是一种在组合数学中出现的数,它表示的是将一个集合划分为非空子集的方式的数量。程序使用了动态规划的方法来计算 Bell 数。

以下是程序的主要步骤和思路:

  1. 初始化动态规划数组dp 是一个二维数组,其中 dp[i][j] 表示的是将 i 个元素划分为 j 个非空子集的方式的数量。初始化时,所有的 dp[i][j] 都被设置为 -1,表示这个值还没有被计算过。

  2. 定义递归函数f(n, m) 是一个递归函数,它返回的是将 n 个元素划分为 m 个非空子集的方式的数量。在这个函数中,我们首先检查 dp[n][m] 是否已经被计算过,如果已经被计算过,那么直接返回 dp[n][m]。否则,我们需要计算 dp[n][m]。计算 dp[n][m] 的方式是使用了组合数学中的一个公式:dp[n][m] = dp[n-1][m-1] + m * dp[n-1][m]。这个公式的含义是,将 n 个元素划分为 m 个非空子集的方式的数量,等于将 n-1 个元素划分为 m-1 个非空子集的方式的数量(即将最后一个元素单独作为一个子集),加上将 n-1 个元素划分为 m 个非空子集的方式的数量(即将最后一个元素加入到已有的 m 个子集中的任意一个)。

  3. 计算 Bell 数:在 main 函数中,我们读入 n,然后计算将 n 个元素划分为任意数量的非空子集的方式的数量,这就是 Bell 数。计算 Bell 数的方式是将 f(n, i)i1n 的所有值求和。

  4. 输出结果:最后,我们输出计算得到的 Bell 数。

这个程序的优化主要在于避免了重复计算。在计算 dp[n][m] 时,我们预先计算并存储了 dp[n-1][m-1]dp[n-1][m] 的值。在计算 Bell 数时,我们预先计算并存储了 f(n, i) 的值。这样,当我们需要这些值时,就可以直接从 dp 数组中取出,而不需要重新计算。

代码实现
#include 
#include 
using namespace std;

vector<vector<long long int>> dp;

long long int f(int n, int m){
    if(dp[n][m] != -1) return dp[n][m];
    if(n < m) return dp[n][m] = 0;
    if(n == m || m == 1) return dp[n][m] = 1;
    dp[n][m] = f(n-1, m-1) + m * f(n-1, m);
    return dp[n][m];
}

int main() {
    int n;
    while(cin >> n){
        if(n == -1) return 0;
        if(n==0){
            cout<<1<<endl;
            continue;
        }
        dp = vector<vector<long long int>>(n+1, vector<long long int>(n+1, -1));
        long long int result = 0;
        for(int i = 1; i <= n; i++){
            result += f(n, i);
        }
        cout << result << endl;
    }
    return 0;
}


你可能感兴趣的:(c++,算法)