NeFut Logo NeFut
Admin Login

In-depth Exploration of Catalan and Stirling Numbers Dynamic Programming Implementation

Published at: 2026-05-27 09:17 Last updated: 2026-06-08 08:56
#C++ #Tutorial

Core Logic and Mathematical Principles

1. Catalan Number

The Catalan number is one of the most frequently encountered special counting sequences in combinatorial mathematics. Let the $n$-th term be denoted as $H_n$, with the standard formula given by:

$$H_n = \frac{C_{2n}^n}{n + 1} = C_{2n}^n - C_{2n}^{n-1}$$

The general term satisfies the following efficient algebraic recurrence relation:

$$H_n = \frac{4n - 2}{n + 1} H_{n-1} \, (H_0 = 1)$$

Proof via Lattice Path Model

In the Cartesian coordinate system, starting from $(0,0)$, if one can only move right or up by 1 unit at a time, we seek the number of non-decreasing paths to $(n,n)$ such that for any point $(x,y)$ on the path, $x \geq y$ (i.e., not crossing the line $y=x$).

2. Stirling Number of the First Kind

The signed Stirling number of the first kind $\begin{bmatrix} n \\ k \end{bmatrix}$ (denoted as $s(n,k)$) represents the number of ways to arrange $n$ distinct elements into $k$ mutually disjoint circular arrangements (cycles). Its state combination recurrence relation is given by:

$$\begin{bmatrix} n \\ k \end{bmatrix} = \begin{bmatrix} n-1 \\ k-1 \end{bmatrix} + (n-1) \cdot \begin{bmatrix} n-1 \\ k \end{bmatrix}$$

3. Stirling Number of the Second Kind

The second kind Stirling number $\begin{Bmatrix} n \\ k \end{Bmatrix}$ (denoted as $S(n,k)$) represents the number of ways to partition $n$ distinct elements into $k$ non-empty indistinguishable subsets (sets). Its state merging recurrence relation is given by:

$$\begin{Bmatrix} n \\ k \end{Bmatrix} = \begin{Bmatrix} n-1 \\ k-1 \end{Bmatrix} + k \cdot \begin{Bmatrix} n-1 \\ k \end{Bmatrix}$$


State Design and Algorithm Derivation

In NOIP practical contests, for counting problems like Stirling numbers that exhibit obvious two-dimensional dependency characteristics, a dynamic programming two-dimensional table filling method is typically employed.

1. State Derivation Logic Analysis for Second Kind Stirling Numbers

Consider the direction of the $n$-th element (the newly added element):

2. State Derivation Logic Analysis for First Kind Stirling Numbers

Consider the direction of the $n$-th element in the circular arrangement:


C++ Standard Source Code

#include <iostream>
#include <vector>

const int MOD = 1000000007;
const int MAXN = 2000;

long long catalan[MAXN + 5];
long long S2[MAXN + 5][MAXN + 5];
long long S1[MAXN + 5][MAXN + 5];

// Preprocess Catalan numbers using linear recurrence
void init_catalan(int n) {
    catalan[0] = 1;
    catalan[1] = 1;
    // Use Fermat's little theorem to find the inverse of the denominator for single-point linear derivation
    auto quick_power = [](long long base, long long exp) {
        long long res = 1;
        while (exp > 0) {
            if (exp & 1) res = (res * base) % MOD;
            base = (base * base) % MOD;
            exp >>= 1;
        }
        return res;
    };

    for (int i = 2; i <= n; ++i) {
        long long inv = quick_power(i + 1, MOD - 2);
        catalan[i] = (4 * i - 2) * catalan[i - 1] % MOD * inv % MOD;
    }
}

// Preprocess the matrices of the two kinds of Stirling numbers
void init_stirling(int n) {
    S1[0][0] = 1;
    S2[0][0] = 1;

    for (int i = 1; i <= n; ++i) {
        S1[i][0] = 0;
        S2[i][0] = 0;
        for (int j = 1; j <= i; ++j) {
            // State transition for the first kind Stirling number
            S1[i][j] = (S1[i - 1][j - 1] + (i - 1) * S1[i - 1][j]) % MOD;
            // State transition for the second kind Stirling number
            S2[i][j] = (S2[i - 1][j - 1] + j * S2[i - 1][j]) % MOD; // Critical point: the multiplier here is j, not i-1
        }
    }
}

int main() {
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);

    init_catalan(MAXN);
    init_stirling(MAXN);

    int n, k;
    if (std::cin >> n >> k) {
        if (n <= MAXN && k <= n) {
            std::cout << "Catalan(" << n << ") = " << catalan[n] << "\n";
            std::cout << "S1(" << n << "," << k << ") = " << S1[n][k] << "\n";
            std::cout << "S2(" << n << "," << k << ") = " << S2[n][k] << "\n";
        }
    }
    return 0;
}

NOIP Practical Contest Pitfall Guide

  1. Confusing the transition multipliers of the first and second kind Stirling numbers Contestants often misremember the multipliers under pressure. The first kind multiplies by the total number of predecessors $(i - 1)$ (representing the number of insertion positions), while the second kind multiplies by the current target set count $j$ (representing the number of chosen subsets). If these two numbers are swapped, the data characteristics of the recurrence table will undergo fundamental changes.
  2. Forgetting to compute the inverse in the linear recurrence of Catalan numbers leading to integer division truncation In the formula $H_n = \frac{4n-2}{n+1} H_{n-1}$, the numerator is divided by $n+1$. In modular arithmetic, directly using the C++ / operator effectively truncates the intermediate result, rather than performing modular division. One must correctly compute the inverse of $(n + 1)$ under modulo $MOD$ using fast exponentiation before executing multiplication.

Classic NOIP/Luogu Problems

1. Luogu P3941 Entering the Formation / Variant Luogu P5386 [CERC2018] Game on Tree (involving second kind Stirling numbers)

2. Luogu P1655 The Child's Balls

Original Source: local://20.4

[h] Back to Home