「CodeNote」 n的m划分

Posted by Dawn-K's Blog on April 23, 2020

m划分问题

给出n个物品(完全一致,无法区分),要求分成m组,每一组可空,求划分数(组之间不考虑顺序)

dp[i][j]表示j的i划分数

那么我们可以将j的i划分,分为两种类型

  1. 每一组都不空
  2. 存在某一组或多组空 显然这两个加起来就是 j 的 i 划分的总数,且这两个没有交集,是最优子结构

每一组都不空

我们先考虑每一组都不空的情况(显然有j>=i).既然每一组都不空,我们索性从每个组中抽出一个物品,则抽取完后,也就是j-i个元素,分到i个组中,每个组都可空.也就是dp[i][j-i]

至少有一种空

我们在考虑至少有一组空的情况.既然至少有一组空,我们就拿走一个空的组,那么还是j个物品,然后分到i-1个组中,每一组可空.也就是dp[i-1][j]


总结

这样我们就得到了转移公式dp[i][j]=dp[i][j-1]+dp[i-1][j]

另外我们再讨论一些边界情况

  • 首先,在x=1或0时 dp[n][x]恒为1,也就是无论n是多少,也就是只有一个元素(或没有元素)时,也只有一种划分方法,这个是(1)的终点
  • 其次,在x=1或0时 dp[x][n] 恒为 1,也就是无论n是多少,只要划分成一组,就只有一种划分方法. (2)中的终点就是分组为1

AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// POJ-1664
// 这个题比较奇葩,是m的n划分,为了和题意一样,导致后面有点绕,但是递推公式是对的
#include <iostream>
using namespace std;
int dp[15][15];
int main() {
    int T;
    cin >> T;
    while (T--) {
        int n, m;
        cin >> m >> n;
        for (int i = 0; i <= n; ++i) {
            for (int j = 0; j <= m; ++j) {
                dp[i][j] = 0;
            }
        }
        for (int i = 0; i <= n; ++i) {
            dp[i][0] = 1;
            dp[i][1] = 1;
        }
        for (int j = 0; j <= m; ++j) {
            dp[0][j] = 1;
            dp[1][j] = 1;
        }
        for (int i = 2; i <= n; ++i) {
            for (int j = 1; j <= m; ++j) {
                if (j >= i) {
                    dp[i][j] = dp[i - 1][j] + dp[i][j - i];
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        cout << dp[n][m] << endl;
    }
    return 0;
}