分割数

[参考元]
・秋葉拓哉,岩田陽一,北川宜稔「プログラミングコンテストチャレンジブック第2版」株式会社マイナビ、2010,pp.66-67
分割数-Wikipedia
動的計画法

蟻本の「分割数」が理解するのが難しかったのでメモ。

蟻本の「分割数」の問題文は
n個の互いに区別できない品物を、m個以下に分割する方法の総数を求め、Mで割った余りを答えなさい。
蟻本より

この問題文を別の言い方で表すと、「自然数nをその順番の違いを除いてm以下の和で表す方法の総数を求め、Mで割った余りを答えなさい。」となる。これを、p(n, m)と表す。

例)
n = 4, m = 3の場合を考える。品物を○、分割を|で表す。
○と|の列が蟻本の問題文の解釈、下の列が別の言い方で表した場合。

○ | ○ | ○○
1 + 1 + 2

となる。
ただし、次の分け方は下の分け方と同じである。

○ | ○○ | ○
1 + 2  + 1

[1] p(n, m)を深さ優先探索で求める。

p(n', m')はp(n', m' - 1)とp(n' - m', m')の和として表せる。
・p(n', m' - 1)はn'をm'-1以下の和で表した場合の総数。すなわち、n'を表すのにm'を使わなかった場合。
・p(n' - m', m')はnを表すのに一つのm'を使った場合の総数。すなわち、n'を表すのにm'を使った場合。

ただし、p(0, m')=1、p(n',1)=1である。
また、mの値が減少していくように探索しているので、1+1+2や1+2+1などを2+1+1と一通りで数える。

#include <iostream>

using namespace std;

const int MAX_N = 1000;
const int MAX_M = 1000;

int n, m, M;

// 深さ優先探索
int p(int n, int m)
{
    int ret;

    if(n == 0 || m == 1)
        return 1;

    ret = p(n, m - 1);
    if(n >= m)
        ret += p(n - m, m);

    return ret;
}

void solve()
{
    cout << p(n, m) % M << endl;
    
}

int main()
{
    cin >> n >> m >> M;
    solve();

    return 0;
}

[2]メモ化をする

大きなnに対しては、p(n, m)が何度か呼ばれていることが分かる。
なので、引数の組(n, m)に対してメモ化を行う。
main関数は省略

#include <iostream>
#include <cstring>

using namespace std;

const int MAX_N = 1000;
const int MAX_M = 1000;

int n, m, M;
int memo[MAX_N + 1][MAX_M + 1]; // メモ化

int p(int n, int m)
{
    int ret;

    if (memo[n][m] >= 0)
        return memo[n][m];

    if(n == 0 || m == 1)
        return 1;

    ret = p(n, m - 1);
    if(n >= m)
        ret += p(n - m, m);

    return memo[n][m] = ret;
}

void solve()
{
    memset(memo, -1, sizeof(memo));
    cout << p(n, m) % M << endl;
}

[3]動的計画法

[2]のメモ化を動的計画法に持ち込む。
※蟻本ではdp[i][j] := jのi分割の総数としているが、
上の[2]に合わせるためにdp[i][j] := iのj分割の総数とした。
初期化の部分は[2]と対応をとりやすいように冗長に書いた。

#include <iostream>
#include <cstring>

using namespace std;

const int MAX_N = 1000;
const int MAX_M = 1000;

int n, m, M;
int dp[MAX_N + 1][MAX_M + 1]; // dp

void solve()
{
    for (int i = 0; i <= m; i++)
        dp[0][i] = 1;
    for (int i = 0; i <= n; i++)
        dp[i][1] = 1;

    for (int i = 0; i <= n; i++)
        for (int j = 1; j <= m; j++) {
            if (i >= j) 
                dp[i][j] = (dp[i][j - 1] + dp[i - j][j]) % M;
            else
                dp[i][j] = dp[i][j - 1];
        }
    
    cout << dp[n][m] << endl;
}