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

6.2.5 Enumerating Partitions

6-Mathematics/6.2.5_Enumerating_Partitions.cpp

View on GitHub

A partition of a natural number $n$ is a way to write $n$ as a sum of positive integers where the order of the addends does not matter.

  • next_partition(p) takes a reference to a vector p of positive integers as a partition of $n$ for which the function will re-assign to become the next lexicographically greater partition. The function returns true if such a partition exists, or false if p already consists of the lexicographically greatest partition (i.e. the single integer $n$).
  • partition_by_rank(n, r) returns the partition of $n$ that is lexicographically ranked $r$ if addends in each partition were sorted in non-increasing order, where $r$ is a 0-based rank in the range $[0, \text{partitions}(n))$.
  • rank_by_partition(p) returns an integer representing the 0-based rank of the partition specified by vector p, which must consist of positive integers sorted in non-increasing order.
  • generate_increasing_partitions(n, f) calls the function f(lo, hi) on strictly increasing partitions of $n$ in lexicographically increasing order of partition, where lo and hi are random-access iterators to a range [lo, hi) of integers. Note that non-strictly increasing partitions like $\{1, 1, 1, 1\}$ are skipped.

Implementation

#include <cstdint>
#include <vector>

bool next_partition(std::vector<int> &p) {
  int n = static_cast<int>(p.size());
  if (n <= 1) {
    return false;
  }
  int s = p[n - 1] - 1, i = n - 2;
  p.pop_back();
  for (; i > 0 && p[i] == p[i - 1]; i--) {
    s += p[i];
    p.pop_back();
  }
  for (p[i]++; s > 0; s--) {
    p.push_back(1);
  }
  return true;
}

int64_t partition_function(int a, int b) {
  static std::vector<std::vector<int64_t>> p(1, std::vector<int64_t>(1, 1));
  if (a >= static_cast<int>(p.size())) {
    int old = static_cast<int>(p.size());
    p.resize(a + 1);
    p[0].resize(a + 1);
    for (int i = 1; i <= a; i++) {
      p[i].resize(a + 1);
      for (int j = old; j <= i; j++) {
        p[i][j] = p[i - 1][j - 1] + p[i - j][j];
      }
    }
  }
  return p[a][b];
}

std::vector<int> partition_by_rank(int n, int64_t r) {
  std::vector<int> res;
  for (int i = n, j; i > 0; i -= j) {
    for (j = 1;; j++) {
      int64_t count = partition_function(i, j);
      if (r < count) {
        break;
      }
      r -= count;
    }
    res.push_back(j);
  }
  return res;
}

int64_t rank_by_partition(const std::vector<int> &p) {
  int64_t res = 0;
  int sum = 0;
  for (int x : p) {
    sum += x;
  }
  for (int x : p) {
    for (int j = 0; j < x; j++) {
      res += partition_function(sum, j);
    }
    sum -= x;
  }
  return res;
}

using Fn = void (*)(std::vector<int>::iterator, std::vector<int>::iterator);

void generate_increasing_partitions(int left, int prev, int i, std::vector<int> &p, Fn f) {
  if (left == 0) {
    f(p.begin(), p.begin() + i);
    return;
  }
  for (p[i] = prev + 1; p[i] <= left; p[i]++) {
    generate_increasing_partitions(left - p[i], p[i], i + 1, p, f);
  }
}

void generate_increasing_partitions(int n, Fn f) {
  std::vector<int> p(n, 0);
  generate_increasing_partitions(n, 0, 0, p, f);
}

Example Usage

#include <cassert>
#include <iostream>
using namespace std;

template<class It>
void print_range(It lo, It hi) {
  cout << "{";
  for (; lo != hi; ++lo) {
    cout << *lo << (lo == hi - 1 ? "" : ",");
  }
  cout << "} ";
}

int main() {
  {
    int n = 4;
    vector<int> a(n, 1);
    cout << "Partitions of " << n << ":" << endl;
    int count = 0;
    do {
      print_range(a.begin(), a.end());
      vector<int> b = partition_by_rank(n, count);
      assert(equal(a.begin(), a.end(), b.begin()));
      assert(rank_by_partition(a) == count);
      count++;
    } while (next_partition(a));
    cout << endl;
  }
  {
    int n = 8;
    cout << "\nIncreasing partitions of " << n << ":" << endl;
    generate_increasing_partitions(n, print_range);
    cout << endl;
  }
  return 0;
}

Example Output

Partitions of 4:
{1,1,1,1} {2,1,1} {2,2} {3,1} {4}

Increasing partitions of 8:
{1,2,5} {1,3,4} {1,7} {2,6} {3,5} {8}