web-dev-qa-db-ja.com

組み合わせの量を計算する

乾杯、

私はあなたが次の式で組み合わせの量を得ることができることを知っています(繰り返しと順序は重要ではありません):

// n 
 
 nからrを選択します!/r!(n-r)!

ただし、これをC++で実装する方法がわかりません。

n = 52 
 
 n! = 8,0658175170943878571660636856404e + 67

unsigned __int64(またはunsigned long long)の場合でも、数値が大きくなりすぎます。サードパーティの「bigint」ライブラリを使用せずに式を実装するための回避策はありますか?

26
nhaa123

これは正確で、結果が_long long_に対して大きくならない限りオーバーフローしない古いアルゴリズムです。

_unsigned long long
choose(unsigned long long n, unsigned long long k) {
    if (k > n) {
        return 0;
    }
    unsigned long long r = 1;
    for (unsigned long long d = 1; d <= k; ++d) {
        r *= n--;
        r /= d;
    }
    return r;
}
_

このアルゴリズムは、Knuthの「TheArt of Computer Programming、3rd Edition、Volume 2:SeminumericalAlgorithms」にも含まれていると思います。

PDATE:アルゴリズムが次の行でオーバーフローする可能性がわずかにあります。

_r *= n--;
_

非常に大きいnの場合。ナイーブな上限はsqrt(std::numeric_limits<long long>::max())です。これは、nがおよそ4,000,000,000未満であることを意味します。

39
Andreas Brinck

から アンドレアスの答え

これは正確で、結果が_long long_に対して大きくならない限りオーバーフローしない古いアルゴリズムです。

_unsigned long long
choose(unsigned long long n, unsigned long long k) {
    if (k > n) {
        return 0;
    }
    unsigned long long r = 1;
    for (unsigned long long d = 1; d <= k; ++d) {
        r *= n--;
        r /= d;
    }
    return r;
}
_

このアルゴリズムは、Knuthの「TheArt of Computer Programming、3rd Edition、Volume 2:SeminumericalAlgorithms」にも含まれていると思います。

UPDATE:アルゴリズムが次の行でオーバーフローする可能性がわずかにあります。

_r *= n--;
_

forverylargen。素朴な上限はsqrt(std::numeric_limits<long long>::max())です。これは、nが粗雑な4,000,000,000未満であることを意味します。

N == 67およびk == 33について考えてみます。上記のアルゴリズムは、64ビットのunsigned longlongでオーバーフローします。それでも、正解は64ビットで表現できます:14,226,520,737,620,288,370。そして、上記のアルゴリズムはそのオーバーフローについて沈黙しています、choose(67、33)は以下を返します:

8,829,174,638,479,413

信じられるが間違った答え。

ただし、上記のアルゴリズムは、最終的な答えが表現可能である限り、オーバーフローしないようにわずかに変更できます。

秘訣は、各反復で除算r/dが正確であることを認識することです。一時的に書き直す:

_r = r * n / d;
--n;
_

これを正確に言うと、r、n、dを素因数分解に展開すると、dを簡単にキャンセルして、nの値を変更したままにして、tと呼ぶことができ、rの計算は次のようになります。単に:

_// compute t from r, n and d
r = r * t;
--n;
_

これを行うための迅速で簡単な方法は、rとdの最大公約数を見つけることです。これをgと呼びます。

_unsigned long long g = gcd(r, d);
// now one can divide both r and d by g without truncation
r /= g;
unsigned long long d_temp = d / g;
--n;
_

これで、d_tempとnを使用して同じことができます(最大公約数を見つけます)。ただし、r * n/dが正確であることが事前にわかっているので、gcd(d_temp、n)== d_tempもわかっているので、計算する必要はありません。したがって、nをd_tempで割ることができます。

_unsigned long long g = gcd(r, d);
// now one can divide both r and d by g without truncation
r /= g;
unsigned long long d_temp = d / g;
// now one can divide n by d/g without truncation
unsigned long long t = n / d_temp;
r = r * t;
--n;
_

清掃:

_unsigned long long
gcd(unsigned long long x, unsigned long long y)
{
    while (y != 0)
    {
        unsigned long long t = x % y;
        x = y;
        y = t;
    }
    return x;
}

unsigned long long
choose(unsigned long long n, unsigned long long k)
{
    if (k > n)
        throw std::invalid_argument("invalid argument in choose");
    unsigned long long r = 1;
    for (unsigned long long d = 1; d <= k; ++d, --n)
    {
        unsigned long long g = gcd(r, d);
        r /= g;
        unsigned long long t = n / (d / g);
        if (r > std::numeric_limits<unsigned long long>::max() / t)
           throw std::overflow_error("overflow in choose");
        r *= t;
    }
    return r;
}
_

これで、オーバーフローなしでchoose(67、33)を計算できます。そして、choose(68、33)を実行しようとすると、間違った答えではなく例外が発生します。

28
Howard Hinnant

次のルーチンは、再帰的定義とメモ化を使用して、n-choose-kを計算します。ルーチンは非常に高速で正確です:

inline unsigned long long n_choose_k(const unsigned long long& n,
                                     const unsigned long long& k)
{
   if (n  < k) return 0;
   if (0 == n) return 0;
   if (0 == k) return 1;
   if (n == k) return 1;
   if (1 == k) return n;       
   typedef unsigned long long value_type;
   value_type* table = new value_type[static_cast<std::size_t>(n * n)];
   std::fill_n(table,n * n,0);
   class n_choose_k_impl
   {
   public:

      n_choose_k_impl(value_type* table,const value_type& dimension)
      : table_(table),
        dimension_(dimension)
      {}

      inline value_type& lookup(const value_type& n, const value_type& k)
      {
         return table_[dimension_ * n + k];
      }

      inline value_type compute(const value_type& n, const value_type& k)
      {
         if ((0 == k) || (k == n))
            return 1;
         value_type v1 = lookup(n - 1,k - 1);
         if (0 == v1)
            v1 = lookup(n - 1,k - 1) = compute(n - 1,k - 1);
         value_type v2 = lookup(n - 1,k);
         if (0 == v2)
            v2 = lookup(n - 1,k) = compute(n - 1,k);
         return v1 + v2;
      }

      value_type* table_;
      value_type dimension_;
   };
   value_type result = n_choose_k_impl(table,n).compute(n,k);
   delete [] table;
   return result;
}
6
Matthieu N.

それを覚えておいてください

n! / ( n - r )! = n * ( n - 1) * .. * (n - r + 1 )

だからnよりずっと小さい!したがって、解決策は、最初にnを計算する代わりに、n *(n --1)* ... *(n --r + 1)を評価することです。そしてそれを分割します。

もちろん、それはすべてnとrの相対的な大きさに依存します-rがnと比較して比較的大きい場合、それでも適合しません。

4
altariste

さて、私は自分の質問に答えなければなりません。パスカルの三角形について読んでいたところ、偶然、パスカルの三角形との組み合わせの量を計算できることに気づきました。

#include <iostream>
#include <boost/cstdint.hpp>

boost::uint64_t Combinations(unsigned int n, unsigned int r)
{
    if (r > n)
        return 0;

    /** We can use Pascal's triange to determine the amount
      * of combinations. To calculate a single line:
      *
      * v(r) = (n - r) / r
      *
      * Since the triangle is symmetrical, we only need to calculate
      * until r -column.
      */

    boost::uint64_t v = n--;

    for (unsigned int i = 2; i < r + 1; ++i, --n)
        v = v * n / i;

    return v;
}

int main()
{
    std::cout << Combinations(52, 5) << std::endl;
}
2
nhaa123

二項係数の素因数分解を取得することは、特に乗算にコストがかかる場合、おそらく最も効率的な計算方法です。これは、階乗の計算に関連する問題にも確かに当てはまります(たとえば、 ここをクリック を参照)。

これは、素因数分解を計算するエラトステネスのふるいに基づく単純なアルゴリズムです。基本的には、ふるいを使用して素数を見つけたときに素数を調べますが、[1、k]と[n-k + 1、n]の範囲に含まれる倍数の数を計算することもできます。ふるいは本質的にO(n\log\log n)アルゴリズムですが、乗算は行われません。素因数分解が見つかったときに必要な実際の乗算数は、最悪の場合O\left(\ frac {n\log\log n} {\ log n}\right)であり、おそらくそれよりも速い方法があります。

prime_factors = []

n = 20
k = 10

composite = [True] * 2 + [False] * n

for p in xrange(n + 1):
if composite[p]:
    continue

q = p
m = 1
total_prime_power = 0
prime_power = [0] * (n + 1)

while True:

    prime_power[q] = prime_power[m] + 1
    r = q

    if q <= k:
        total_prime_power -= prime_power[q]

    if q > n - k:
        total_prime_power += prime_power[q]

    m += 1
    q += p

    if q > n:
        break

    composite[q] = True

prime_factors.append([p, total_prime_power])

 print prime_factors
1
user4027538

ただし、最初に式を単純化してください。筆算はしたくない。

0
glebm

ロングダブルでダーティトリックを使用すると、ハワードヒナントと同じ精度(そしておそらくそれ以上)を得ることができます:

unsigned long long n_choose_k(int n, int k)
{
    long double f = n;
    for (int i = 1; i<k+1; i++)
        f /= i;
    for (int i=1; i<k; i++)
        f *= n - i;

    unsigned long long f_2 = std::round(f);

    return f_2;
}

アイデアは最初にkで割ることです!次に、n(n-1)...(n-k + 1)を掛けます。 forループの順序を逆にすることで、doubleによる近似を回避できます。

0
R.Falque

最も短い方法の1つ:

int nChoosek(int n, int k){
    if (k > n) return 0;
    if (k == 0) return 1;
    return nChoosek(n - 1, k) + nChoosek(n - 1, k - 1);
}
0
online6731