Skip to content

集合划分问题

2-7

2-8

2-7 解题

1、divide-and-conquer

cnblogs 集合划分问题

思路:对于n个元素的集合,可以划分成由m(1<=m<=n)个子集构成的子集,如 {{1},{2},{3},{4}}就是由4个子集构成的非空子集。假设f(n,m)表示将n个元素的集合划分成由m个子集构成的集合的个数,那么可以这样来看:

1)若m==1,则f(n,m)=1;

2)若n==m,则f(n,m)=1;

3)若非以上两种情况,f(n,m)可以由下面两种情况构成

​ a.向n-1个元素划分成的m个集合里面添加一个新的元素,则有m*f(n-1,m)种方法;

​ b.向n-1个元素划分成的m-1个集合里添加一个由一个元素形成的独立的集合,则有f(n-1,m-1)种方法。

因此:

$1 (m==1||n==m) $

f(n,m)=f(n-1,m-1)+m*f(n-1,m) (m<n \&\&m!=1)

递归实现

#include<stdio.h>
/**
 * @brief 将元素个数为n的结合,划分为包含m个子集的
 *
 * @param n 元素个数
 * @param m 子集个数
 * @return
 */
int f(int n, int m)
{
    if (m == 1 || n == m)
        return 1;
    else
        return f(n - 1, m - 1) + f(n - 1, m) * m;
}

int main(void)
{
    int n;
    while (scanf("%d", &n) == 1 && n >= 1)
    {
        int i;
        int sum = 0;
        for (i = 1; i <= n; i++)
        {
            sum += f(n, i);
        }
        printf("%d\n", sum);
    }
    return 0;
}
// gcc test.cpp

DP实现

csdn 集合划分问题

NOTE:

1、给出的code非常好

/*
 Name: 集合划分问题
 Copyright:
 Author:
 Date: 01-06-18 10:29
 Description: 集合划分问题
 题目描述
 设S是一个具有n个元素的集合,S=?a1,a2,……,an?S=?a1,a2,……,an?,
 现将S划分成k个满足下列条件的子集合S1,S2,……,SkS1,S2,……,Sk ,且满足:
 1.Si ≠ ?
 2.Si ∩ Sj = ?            (1≤i,j≤k  i≠j)
 3.S1 ∪ S2 ∪ S3 ∪ … ∪ Sk = S
 则称S1,S2,……,SkS1,S2,……,Sk是集合S的一个划分。它
 相当于把S集合中的n个元素a1,a2,……,ana1,a2,……,an 放入k个(0<k≤n<30)无标号的盒子中,使得没有一个盒子为空。
 请你确定n个元素a1,a2,……,ana1,a2,……,an 放入k个无标号盒子中去的划分数S(n,k)。
 输入
 给出n和k。
 输出
 n个元素a1,a2,……,ana1,a2,……,an 放入k个无标号盒子中去的划分数S(n,k)。
 样例输入
 10 6
 样例输出
 22827
 例如,当n=4,k=3时,集合{1,2,3,4}可以划分为S(4,3)=6:
 {{1,2},{3},{4}},
 {{1,3},{2},{4}},
 {{1,4},{2},{3}},
 {{2,3},{1},{4}},
 {{2,4},{1},{3}},
 {{3,4},{1},{2}}
 算法分析:
 假设f(k,n)表示将n个元素的集合划分成由k个子集构成的集合的个数,那么可以这样来看:
 1)若k==0或n<k,则f(k,n)=0;
 2)若k==1或n==k,则f(k,n)=1;
 3)若非以上两种情况,f(n,m)可以由下面两种情况构成
 a.向n-1个元素划分成的k个集合里面添加一个新的元素,则有k*f(k,n-1)种方法;
 b.向n-1个元素划分成的k-1个集合里添加一个由一个元素形成的独立的集合,则有f(k-1,n-1)种方法。
 即 f(k,n)=f(k-1,n-1)+k*f(k,n-1)       (k<n&&k!=1)
 有了上述递推式,我们就分别可以用递归,记忆化搜索和动态规划算法来实现它。
 */
#include<iostream>
#include<cstring>

using namespace std;

const int MAXK = 10; //子集最大数量
const int MAXN = 200; //元素最大个数

int B2[MAXK + 1][MAXN + 1]; //备忘录,记录将n个元素的集合划分成由k个子集构成的集合的个数
int B3[MAXK + 1][MAXN + 1]; //备忘录,记录将n个元素的集合划分成由k个子集构成的集合的个数

int pre[MAXN + 1]; //记录上一行元素值
int cur[MAXN + 1]; //记录当前行元素值

int S(int k, int n); //递归算法:返回将n个元素的集合划分成由k个子集构成的集合的个数
int S2(int k, int n); //记忆化搜索:返回将n个元素的集合划分成由k个子集构成的集合的个数
int S3(int k, int n); //动态规划:返回将n个元素的集合划分成由k个子集构成的集合的个数
int S4(int k, int n); //优化的动态规划:返回将n个元素的集合划分成由k个子集构成的集合的个数

int main()
{
    int n, k;
    cin >> n >> k;

    cout << S(k, n) << endl;

    memset(B2, -1, sizeof(B2)); //先初始化B2的值全为-1
    cout << S2(k, n) << endl;

    cout << S3(k, n) << endl;
    cout << S4(k, n) << endl;

    return 0;
}

/**
 * @brief 递归算法:返回将n个元素的集合划分成由k个子集构成的集合的个数
 *
 * @param k
 * @param n
 * @return
 */
int S(int k, int n)
{
    if (k == 0 || n < k)
        return 0;
    if (k == 1 || n == k)
        return 1;
    return S(k - 1, n - 1) + k * S(k, n - 1);
}

/**
 * @brief 记忆化搜索:返回将n个元素的集合划分成由k个子集构成的集合的个数
 *
 * @param k
 * @param n
 * @return
 */
int S2(int k, int n)
{
    if (B2[k][n] != -1)
        return B2[k][n];
    if (k == 0 || n < k)
        B2[k][n] = 0;
    else if (k == 1 || n == k)
        B2[k][n] = 1;
    else
        B2[k][n] = S2(k - 1, n - 1) + k * S2(k, n - 1);

    return B2[k][n];
}

/**
 * @brief 动态规划:返回将n个元素的集合划分成由k个子集构成的集合的个数
 *
 * @param k
 * @param n
 * @return
 */
int S3(int k, int n)
{
    for (int j = 1; j <= n; j++)
        B3[j][j] = B3[1][j] = 1;

    for (int i = 2; i <= k; i++) // 集合个数
    {
        for (int j = i + 1; j <= n; j++) // 元素个数
        {
            B3[i][j] = B3[i - 1][j - 1] + i * B3[i][j - 1];
        }
    }

    return B3[k][n];
}

/**
 * @brief 优化的动态规划:返回将n个元素的集合划分成由k个子集构成的集合的个数
 *
 * @param k
 * @param n
 * @return
 */
int S4(int k, int n)
{
    for (int j = 1; j <= n; j++) //记录第一行数据,即只包含一个集合
    {
        pre[j] = 1;
    }
    for (int i = 2; i <= k; i++) // 集合个数
    {
        cur[i] = 1; //相当于B3[j][j] = 1;
        for (int j = i + 1; j <= n; j++) // 元素个数
        {
            /**
             * 对比来看:
             * B3[i][j] = B3[i - 1][j - 1] + i * B3[i][j - 1];
             *
             * B3[i - 1][j - 1] 表示的上一行
             * B3[i][j - 1] 表示的是当前行
             */
            cur[j] = pre[j - 1] + i * cur[j - 1];
        }
        /**
         * 迭代
         */
        for (int j = 1; j <= n; j++)
        {
            pre[j] = cur[j];
        }
    }

    return pre[n];
}

// g++ test.cpp

"tag-one-by-one-iteration-move next向后滑动一步-previous current迭代"