Alex's Anthology of Algorithms Common Code for Contests in Concise C++
Mathematics / Combinatorics

6.2.1 Combinatorial Calculations

6-Mathematics/6.2.1_Combinatorial_Calculations.cpp

The following functions implement common operations in combinatorics. All size and count inputs must be nonnegative, and all moduli must be positive. All return values and table entries are computed as 64-bit integers modulo an input argument $m$ or $p$.

  • factorial(n, m = MOD) returns $n! \bmod m$.
  • factorial_without_p(n, p = MOD) returns $n!$ modulo the prime $p$ after removing every factor of $p$ from the factorial.
  • binomial_table(n, m = MOD) returns the first $n + 1$ rows of Pascal's triangle as a two-dimensional vector $t$ such that $t[i][j] = \binom{i}{j} \bmod m$.
  • permute(n, k, m = MOD) returns $(n \mathbin{\text{permute}} k) \bmod m$.
  • choose(n, k, p = MOD) returns $\binom{n}{k} \bmod p$, where $p$ is prime and $n < p$.
  • multichoose(n, k, p = MOD) returns $(n \mathbin{\text{multichoose}} k) \bmod p$, where $p$ is prime and $n + k - 1 < p$ when $n > 0$.
  • catalan(n, p = MOD) returns the $n$-th Catalan number mod $p$, where $p$ is prime and $2n < p$.
  • partitions(n, m = MOD) returns the number of partitions of $n$, mod $m$.
  • partitions(n, k, m = MOD) returns the number of partitions of $n$ into $k$ parts, mod $m$.
  • stirling1(n, k, m = MOD) returns the $(n, k)$ unsigned Stirling number of the 1st kind mod $m$.
  • stirling2(n, k, m = MOD) returns the $(n, k)$ Stirling number of the 2nd kind mod $m$.
  • eulerian1(n, k, m = MOD) returns the $(n, k)$ Eulerian number of the 1st kind mod $m$, where $n > k$.
  • eulerian2(n, k, m = MOD) returns the $(n, k)$ Eulerian number of the 2nd kind mod $m$, where $n > k$.

Overflow warning: Modular products use ordinary int64_t multiplication, so the square of the chosen modulus must fit in int64_t.

Implementation

#include <cstdint>
#include <vector>

const int64_t MOD = 1000000007;

int64_t factorial(int n, int64_t m = MOD) {
  int64_t res = 1;
  for (int i = 2; i <= n; i++) {
    res = (res * i) % m;
  }
  return res % m;
}

int64_t factorial_without_p(int64_t n, int64_t p = MOD) {
  int64_t res = 1;
  while (n > 1) {
    if (n / p % 2 == 1) {
      res = res * (p - 1) % p;
    }
    int max = n % p;
    for (int i = 2; i <= max; i++) {
      res = (res * i) % p;
    }
    n /= p;
  }
  return res % p;
}

std::vector<std::vector<int64_t>> binomial_table(int n, int64_t m = MOD) {
  std::vector<std::vector<int64_t>> t(n + 1);
  for (int i = 0; i <= n; i++) {
    for (int j = 0; j <= i; j++) {
      if (i < 2 || j == 0 || i == j) {
        t[i].push_back(1);
      } else {
        t[i].push_back((t[i - 1][j - 1] + t[i - 1][j]) % m);
      }
    }
  }
  return t;
}

int64_t permute(int n, int k, int64_t m = MOD) {
  if (n < k) {
    return 0;
  }
  int64_t res = 1;
  for (int i = 0; i < k; i++) {
    res = res * (n - i) % m;
  }
  return res % m;
}

int64_t powmod(int64_t x, int64_t n, int64_t m) {
  int64_t a = 1, b = x % m;
  for (; n > 0; n >>= 1) {
    if (n & 1) {
      a = a * b % m;
    }
    b = b * b % m;
  }
  return a % m;
}

int64_t choose(int n, int k, int64_t p = MOD) {
  if (n < k) {
    return 0;
  }
  if (k > n - k) {
    k = n - k;
  }
  int64_t num = 1, den = 1;
  for (int i = 0; i < k; i++) {
    num = num * (n - i) % p;
  }
  for (int i = 1; i <= k; i++) {
    den = den * i % p;
  }
  return num * powmod(den, p - 2, p) % p;
}

int64_t multichoose(int n, int k, int64_t p = MOD) {
  if (n == 0) {
    return k == 0;
  }
  return choose(n + k - 1, k, p);
}

int64_t catalan(int n, int64_t p = MOD) {
  return choose(2 * n, n, p) * powmod(n + 1, p - 2, p) % p;
}

int64_t partitions(int n, int64_t m = MOD) {
  std::vector<int64_t> t(n + 1);
  t[0] = 1;
  for (int i = 1; i <= n; i++) {
    for (int j = i; j <= n; j++) {
      t[j] = (t[j] + t[j - i]) % m;
    }
  }
  return t[n] % m;
}

int64_t partitions(int n, int k, int64_t m = MOD) {
  std::vector<std::vector<int64_t>> t(n + 1, std::vector<int64_t>(k + 1));
  t[0][0] = 1;
  for (int i = 1; i <= n; i++) {
    for (int j = 1, h = k < i ? k : i; j <= h; j++) {
      t[i][j] = (t[i - 1][j - 1] + t[i - j][j]) % m;
    }
  }
  return t[n][k] % m;
}

int64_t stirling1(int n, int k, int64_t m = MOD) {
  std::vector<std::vector<int64_t>> t(n + 1, std::vector<int64_t>(k + 1));
  t[0][0] = 1;
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= k; j++) {
      t[i][j] = (i - 1) * t[i - 1][j] % m;
      t[i][j] = (t[i][j] + t[i - 1][j - 1]) % m;
    }
  }
  return t[n][k] % m;
}

int64_t stirling2(int n, int k, int64_t m = MOD) {
  std::vector<std::vector<int64_t>> t(n + 1, std::vector<int64_t>(k + 1));
  t[0][0] = 1;
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= k; j++) {
      t[i][j] = j * t[i - 1][j] % m;
      t[i][j] = (t[i][j] + t[i - 1][j - 1]) % m;
    }
  }
  return t[n][k] % m;
}

int64_t eulerian1(int n, int k, int64_t m = MOD) {
  if (k > n - 1 - k) {
    k = n - 1 - k;
  }
  std::vector<std::vector<int64_t>> t(n + 1, std::vector<int64_t>(k + 1, 1));
  for (int j = 1; j <= k; j++) {
    t[0][j] = 0;
  }
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= k; j++) {
      t[i][j] = (i - j) * t[i - 1][j - 1] % m;
      t[i][j] = (t[i][j] + ((j + 1) * t[i - 1][j] % m)) % m;
    }
  }
  return t[n][k] % m;
}

int64_t eulerian2(int n, int k, int64_t m = MOD) {
  std::vector<std::vector<int64_t>> t(n + 1, std::vector<int64_t>(k + 1, 1));
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= k; j++) {
      if (i == j) {
        t[i][j] = 0;
      } else {
        t[i][j] = (j + 1) * t[i - 1][j] % m;
        t[i][j] = ((2 * i - 1 - j) * t[i - 1][j - 1] % m + t[i][j]) % m;
      }
    }
  }
  return t[n][k] % m;
}

Example Usage

#include <cassert>

int main() {
  auto t = binomial_table(10);
  for (int i = 0; i < static_cast<int>(t.size()); i++) {
    for (int j = 0; j < static_cast<int>(t[i].size()); j++) {
      assert(t[i][j] == choose(i, j));
    }
  }
  assert(factorial(10) == 3628800);
  assert(factorial_without_p(123456) == 639390503);
  assert(factorial_without_p(7, 5) == 3);  // 7! / 5 = 1008 = 3 (mod 5).
  assert(permute(10, 4) == 5040);
  assert(choose(20, 7) == 77520);
  assert(multichoose(20, 7) == 657800);
  assert(catalan(10) == 16796);
  assert(partitions(4) == 5);
  assert(partitions(0, 0) == 1);
  assert(partitions(100, 5) == 38225);
  assert(stirling1(4, 2) == 11);
  assert(stirling2(4, 3) == 6);
  assert(eulerian1(9, 5) == 88234);
  assert(eulerian2(8, 3) == 195800);
  return 0;
}