algo — C++23 日本語リファレンス GitHub

未検証 · 最終更新 2026-09-07 · GitHub

nの全ての整数分割についてfを呼ぶ

lib/algorithm/integer_partition.hpp

概要

分割は降順、列挙順は辞書順降順。

インクルード

#include "algorithm/integer_partition.hpp"

関数

名前 説明
enumerate nの全ての整数分割についてfを呼ぶ
enumerate_k nをちょうどk個の正整数へ分割した全ての列についてfを呼ぶ
partitions nの全ての整数分割を返す
partitions_k nをちょうどk個に分割した全ての列を返す
実装
#pragma once
#include <algorithm>
#include <concepts>
#include <vector>

/// @brief 整数の分割を列挙する
namespace integer_partition {

// 1 つの分割は降順の正整数列で表す(例: 5 = 3 + 1 + 1 → {3, 1, 1})

/// @brief nの全ての整数分割についてfを呼ぶ
/// @details 分割は降順、列挙順は辞書順降順。
/// @complexity 分割数を $P(n)$ として $O(nP(n))$
template <std::invocable<const std::vector<int> &> F>
void enumerate(int n, F f) {
    if (n < 0) return;
    std::vector<int> part;
    if (n == 0) {
        f(part);
        return;
    }
    part.push_back(n);
    while (true) {
        f(part);
        // 末尾を分解して辞書順で次に小さい降順列を作る。
        // 末尾の連続する 1 の個数を rem に集める。
        int rem = 0;
        while (!part.empty() && part.back() == 1) {
            ++rem;
            part.pop_back();
        }
        if (part.empty()) break;
        // 直前の要素を 1 減らし、それを上限として残り rem + 1 を貪欲に詰める。
        --part.back();
        int mx = part.back();
        rem += 1;
        while (rem > mx) {
            part.push_back(mx);
            rem -= mx;
        }
        part.push_back(rem);
    }
}

/// @brief nをちょうどk個の正整数へ分割した全ての列についてfを呼ぶ
/// @details 各分割は降順。
/// @complexity 対象となる分割数を $P_k(n)$ として $O(kP_k(n))$
template <std::invocable<const std::vector<int> &> F>
void enumerate_k(int n, int k, F f) {
    if (k < 0 || n < 0) return;
    if (k == 0) {
        if (n == 0) {
            std::vector<int> empty;
            f(empty);
        }
        return;
    }
    if (n < k) return;
    std::vector<int> part(k);
    // rec(idx, rem, mx): part[idx..) に合計 rem を mx 以下の降順で配分する
    auto rec = [&](auto &&self, int idx, int rem, int mx) -> void {
        int left = k - idx;  // 残りの個数
        if (left == 1) {
            if (1 <= rem && rem <= mx) {
                part[idx] = rem;
                f(part);
            }
            return;
        }
        // この位置の値 v を大きい順に試す。残り left - 1 個へは各 1 以上 v 以下で配る。
        int hi = std::min(mx, rem - (left - 1));        // 残りを 1 ずつ確保
        int lo = std::max(1, (rem + left - 1) / left);  // 残りが v*(left-1) 以下になる下限
        for (int v = hi; v >= lo; --v) {
            part[idx] = v;
            self(self, idx + 1, rem - v, v);
        }
    };
    rec(rec, 0, n, n);
}

/// @brief nの全ての整数分割を返す
/// @complexity 分割数を $P(n)$ として時間・出力サイズともに $O(nP(n))$
inline std::vector<std::vector<int>> partitions(int n) {
    std::vector<std::vector<int>> res;
    enumerate(n, [&](const std::vector<int> &p) { res.push_back(p); });
    return res;
}

/// @brief nをちょうどk個に分割した全ての列を返す
/// @complexity 対象となる分割数を $P_k(n)$ として時間・出力サイズともに $O(kP_k(n))$
inline std::vector<std::vector<int>> partitions_k(int n, int k) {
    std::vector<std::vector<int>> res;
    enumerate_k(n, k, [&](const std::vector<int> &p) { res.push_back(p); });
    return res;
}

}  // namespace integer_partition

bundled

#line 2 "lib/algorithm/integer_partition.hpp"
#include <algorithm>
#include <concepts>
#include <vector>

/// @brief 整数の分割を列挙する
namespace integer_partition {

// 1 つの分割は降順の正整数列で表す(例: 5 = 3 + 1 + 1 → {3, 1, 1})

/// @brief nの全ての整数分割についてfを呼ぶ
/// @details 分割は降順、列挙順は辞書順降順。
/// @complexity 分割数を $P(n)$ として $O(nP(n))$
template <std::invocable<const std::vector<int> &> F>
void enumerate(int n, F f) {
    if (n < 0) return;
    std::vector<int> part;
    if (n == 0) {
        f(part);
        return;
    }
    part.push_back(n);
    while (true) {
        f(part);
        // 末尾を分解して辞書順で次に小さい降順列を作る。
        // 末尾の連続する 1 の個数を rem に集める。
        int rem = 0;
        while (!part.empty() && part.back() == 1) {
            ++rem;
            part.pop_back();
        }
        if (part.empty()) break;
        // 直前の要素を 1 減らし、それを上限として残り rem + 1 を貪欲に詰める。
        --part.back();
        int mx = part.back();
        rem += 1;
        while (rem > mx) {
            part.push_back(mx);
            rem -= mx;
        }
        part.push_back(rem);
    }
}

/// @brief nをちょうどk個の正整数へ分割した全ての列についてfを呼ぶ
/// @details 各分割は降順。
/// @complexity 対象となる分割数を $P_k(n)$ として $O(kP_k(n))$
template <std::invocable<const std::vector<int> &> F>
void enumerate_k(int n, int k, F f) {
    if (k < 0 || n < 0) return;
    if (k == 0) {
        if (n == 0) {
            std::vector<int> empty;
            f(empty);
        }
        return;
    }
    if (n < k) return;
    std::vector<int> part(k);
    // rec(idx, rem, mx): part[idx..) に合計 rem を mx 以下の降順で配分する
    auto rec = [&](auto &&self, int idx, int rem, int mx) -> void {
        int left = k - idx;  // 残りの個数
        if (left == 1) {
            if (1 <= rem && rem <= mx) {
                part[idx] = rem;
                f(part);
            }
            return;
        }
        // この位置の値 v を大きい順に試す。残り left - 1 個へは各 1 以上 v 以下で配る。
        int hi = std::min(mx, rem - (left - 1));        // 残りを 1 ずつ確保
        int lo = std::max(1, (rem + left - 1) / left);  // 残りが v*(left-1) 以下になる下限
        for (int v = hi; v >= lo; --v) {
            part[idx] = v;
            self(self, idx + 1, rem - v, v);
        }
    };
    rec(rec, 0, n, n);
}

/// @brief nの全ての整数分割を返す
/// @complexity 分割数を $P(n)$ として時間・出力サイズともに $O(nP(n))$
inline std::vector<std::vector<int>> partitions(int n) {
    std::vector<std::vector<int>> res;
    enumerate(n, [&](const std::vector<int> &p) { res.push_back(p); });
    return res;
}

/// @brief nをちょうどk個に分割した全ての列を返す
/// @complexity 対象となる分割数を $P_k(n)$ として時間・出力サイズともに $O(kP_k(n))$
inline std::vector<std::vector<int>> partitions_k(int n, int k) {
    std::vector<std::vector<int>> res;
    enumerate_k(n, k, [&](const std::vector<int> &p) { res.push_back(p); });
    return res;
}

}  // namespace integer_partition