競技プログラミング用の,順列組合せ (重複有り無し) を生成する自作 C++ ライブラリ Perm の使用法です.ソースコードはここ にあります.

使用例

IntPerm ip(10, 3);   // 10 から 3 とる順列
while (ip.get()) {
  for (int i = 0; i < 10; i++) cout << ip[i] << " ";
}

struct

以下の struct がある.名前がおかしいのは歴史的理由による.

IntPerm ip(m, n);         // [0:m) から n 個とる順列
IntComb ic(m, n);         // [0:m) から n 個とる組合せ
IntDupPerm idp(m, n);     // [0:m) から n 個とる重複順列
IntDupComb idc(m, n);     // [0:m) から n 個とる重複組合せ
IntDirProd idi(vector<int>{{m1, m2, ...}}) // 直積 [0:m1) × [0:m2) × ...
IntPartition part(m)      // m の分割

最初の4つは,$[0, m)$ の数からなる長さ $n$ のリストを生成する. このライブラリでは,「順列」と「組合せ」の違いは,後者は昇順にソートされている,ということである.

IntPerm(m, n)

  • $[0, m)$ から $n$ 個とる順列.全部で $m! / (m - n)!$ 個.
  • $[0, m)$ からなる長さ $n$ のリストで,同じ数がたかだか1回しか現れないものを列挙する.
  • たとえば IntPerm(4, 2) は,次を生成する:
    [0,1], [0,2], [0,3], [0,4], [1,0], [1,1], [1,2], [1,3],
    [2,0], [2,1], [2,2], [2,3], [3,0], [3,1], [3,2], [3,3]
    

IntComb(m, n)

  • $[0, m)$ から $n$ 個とる組合せ.全部で $\displaystyle\binom{m}{n}$ 個.
  • $[0, m)$ からなる長さ $n$ のリストで,同じ数がたかだか1回しか現れず,昇順ソートされているものを列挙する.
  • たとえば,IntComb(4, 2) は,次を生成する:
    [0,1], [0,2], [0,3], [0,4], [1,2], [1,3], [2,3]
    

IntDupPerm(m, n)

  • $[0, m)$ から $n$ 個とる重複順列.全部で $m^n$ 個.
  • $[0, m)$ からなる長さ $n$ のリストを列挙する.
  • たとえば idp(3, 2) は,次を生成する:
    [0,0], [0,1], [0,2], [1,0], [1,1], [1,2], [2,0], [2,1], [2,2]
    

IntDupComb(m, n)

  • $[0, m)$ から $n$ 個とる重複組合せ.全部で $\displaystyle\binom{m + n - 1}{n}$ 個.
    • あるいは,$n$ 個のボールを $m$ 個の箱に入れる方法. 箱ごとのボールの数の組合せの列挙は,後述してある
  • $[0, m)$ からなる長さ $n$ のリストで,昇順ソートされているものを列挙する.
  • たとえば idc(4, 2) は,次を生成する:
[0,0], [0,1], [0,2], [0,3], [1,1], [1,2], [1,3], [2,2], [2,3], [3,3]

IntDirProd(vector{m1, m2, …})

  • 直積 $[0, m_1) \times [0, m_2) \times \dots \times [0, m_n]$.全部で $\prod_{i=1}^{n} m_i$ 個.
  • 上の直積の要素を列挙する.
  • たとえば,IntDirProd(vector{1, 2, 3}) は,次を生成する:
    [0,0,0],[0,0,1],[0,0,2],[0,1,0],[0,1,1],[0,1,2]
    

IntPartition(n)

  • $n$ の分割.
  • $n$ の分割の要素を列挙する.
  • たとえば,IntPartition(5) は,次を生成する.
    [[1,1,1,1,1], [1,1,1,2], [1,1,3], [1,2,2], [1,4], [2,3], [5]]
    

生成

obj.get() が true を返す間,新しいリストが生成される. リストの i 番目の要素は,obj.at(i) である.obj[i] でも良い.

while (ip.get()) {
  for (int i = 0; i < 2; i++) cout << ip.at(i) << " ";
}

主としてデバッグ用に,obj.vec_view() も用意されており,const vector<int>& を返す.

const vector<int>& v = ip.vec_view();
for (int i = 0; i < 2; i++) cout << v[i] << " ";

注意1

obj.get() が false を返すときには,内部状態は初期値に戻るので,そのまま次のラウンドを実行することができる.

注意2

  • $n \geq 0, r \geq 0$ でないときには,何も生成しない.
  • IntPerm と IntComb は,$r \leq n$ でないときには,何も生成しない.
  • IntDupPerm と IntDupComb は,$n = r = 0$ のときに,何も生成しないのでは なく,空リスト [] を生成する.これは,ことによると適切でないかもしれない.($0^0 = 1$ になるし,$H(n, r) \neq C(n+r-1, r)$ になるから)
  • $n = 0$ かつ $r \neq 0$ の場合には,何も生成しない.

デバッグ用

const vector<int>& obj.vec_view(); でベクトルに変換できる.

[0, 1, ..., n) 以外の対象

コンストラクタの第3引数に,長さ $m$ のベクトルを置いて,そこから取ってこさせることができる:

  IntComb ip(4, 2, vector{{100, 50, 90, 10}});
  // [[100, 50], [100, 90], [100, 10], [50, 90], [50, 10], [90, 10]]
  IntPerm<char> ip(3, 2, vector<char>{'a', 'b', 'c'});
  // [['a', 'b'], ['a', 'c'], ['b', 'a'], ['b', 'c'], ['c', 'a'], ['c', 'b']]

メソッド set_mapping を使うこともできる.

  IntPerm<char> ip(3, 2);
  ip.set_mapping([](int i) { return "abc"[i]; });
  // 上と同じ

その他

ボールと箱の重複組合せ

区別される $m$ 個の箱があって,そこに区別されない $n$ 個のボールを入れることを考える状況がよくある. これは,$m$ から重複を許して $n$ だけ選ぶ,ということになる. たとえば,$m = 3$ 個の箱 A, B, C があって,そこに $n = 5$ 個のボールをいれて,おのおの 2個,1個,2個入った,ケースを考えよう.これは,このライブラリで言えば,箱 A, B, C に識別番号 0, 1, 2 がついていて,5回の選択で [0, 0, 1, 2, 2] が選ばれた,ということに対応している.

しかし,このような状況では,普通,各々の箱にボールがいくつあるか,ということに興味があろう.上のケースでは [2, 1, 2] を生成してほしいわけである.これは,次のコードで可能になる:

IntDupComb idc(m, n);
while (idc.get()) {
  vector<ll> numBalls(m);
  REP(i, 0, n) numBalls[idc.at(i)]++;
  //  箱 j にはボールが numBalls[j] 個入っている
}

Python での計算

考察時に,だいたいのサイズを知りたいことがある.Python では次のように計算できる.

from math import factorial, perm, comb 
print(perm(m, n))         # IntPerm(m, n)
print(comb(m, n))         # IntComb(m, n)
print(m ** n)             # IntDupPerm(m, n)
print(comb(m + n - 1, n)) # IntDupComb(m, n)
print(factorial(n))       # n!

分割数

  • 10000 までの分割数の表は,https://oeis.org/A000041/b000041.txt にある.

  • 最初のほうのいくつかは次の通り:

// 0  1  2  3  4  5   6   7   8   9  10  11  12   13   14   15   16   17   18   19    //  i
[  1, 1, 2, 3, 5, 7, 11, 15, 22, 30, 42, 56, 77, 101, 135, 176, 231, 297, 385, 490]   // p(i)
p(20) = 627
p(25) = 1958
p(30) = 5604
p(40) = 37338 (3.7E4)
p(50) = 204226 (2.0E5)
p(61) = 1121505 (1.1E6)
p(77) = 10619863 (1.1E7)

キーワード: permutation, combination, duplicate permutation, duplicate combination, binom, partition, direct product