计算大n&的二项式系数(nCk)。 ķ

时间:2010-08-21 11:00:20

标签: c++ algorithm math combinatorics

我刚看到这个问题而不知道如何解决它。你可以请我提供算法,C ++代码或想法吗?

  

这是一个非常简单的问题。给定N和K的值,您需要告诉我们二项式系数C(N,K)的值。您可以放心,K <= N且N的最大值是1,000,000,000,000,000。由于该值可能非常大,因此您需要以1009计算结果模数。   输入

     

输入的第一行包含测试用例T的数量,最多为1000.下一个T行中的每一行由两个空格分隔的整数N和K组成,其中0 <= K <= N且1&lt; ; = N <= 1,000,000,000,000,000。   输出

     

对于每个测试用例,在新行上打印,二项式系数C(N,K)的模数为1009。   实施例

     

输入:
  3
  3 1
  5 2
  10 3

     

输出:
  3
  10个
  120个

5 个答案:

答案 0 :(得分:27)

请注意,1009是素数。

现在您可以使用Lucas' Theorem

哪个州:

Let p be a prime.
If n  = a1a2...ar when written in base p and
if k  = b1b2...br when written in base p

(pad with zeroes if required)

Then

(n choose k) modulo p = (a1 choose b1) * (a2 choose  b2) * ... * (ar choose br) modulo p.

i.e. remainder of n choose k when divided by p is same as the remainder of
the product (a1 choose b1) * .... * (ar choose br) when divided by p.
Note: if bi > ai then ai choose bi is 0.

因此,您的问题减少到找到格式为1009的最多log N / log 1009数字(基数1009中N的位数)的形式a选择b其中a&lt; = 1009且b&lt; = 1009。

即使N接近10 ^ 15,这也应该更容易。

  

注意:

     

对于N = 10 ^ 15,N选择N / 2大于   这是2 ^(100000000000000)的方式   超出无条件的长期。

     

此外,该算法建议   卢卡斯定理是O(log N)   exponentially比尝试更快   计算二项式系数   直接(即使你做了一个mod 1009   照顾溢出问题。)

以下是我曾经写过的Binomial的一些代码,你需要做的就是修改它来做模数1009的操作(可能有bug,不一定推荐编码风格):

class Binomial
{
public:
    Binomial(int Max)
    {
        max = Max+1;
        table = new unsigned int * [max]();
        for (int i=0; i < max; i++)
        {
            table[i] = new unsigned int[max]();

            for (int j = 0; j < max; j++)
            {
                table[i][j] = 0;
            }
        }
    }

    ~Binomial()
    {
        for (int i =0; i < max; i++)
        {
            delete table[i];
        }
        delete table;
    }

    unsigned int Choose(unsigned int n, unsigned int k);

private:
    bool Contains(unsigned int n, unsigned int k);

    int max;
    unsigned int **table;
};

unsigned int Binomial::Choose(unsigned int n, unsigned int k)
{
    if (n < k) return 0;
    if (k == 0 || n==1 ) return 1;
    if (n==2 && k==1) return 2;
    if (n==2 && k==2) return 1;
    if (n==k) return 1;


    if (Contains(n,k))
    {
        return table[n][k];
    }
    table[n][k] = Choose(n-1,k) + Choose(n-1,k-1);
    return table[n][k];
}

bool Binomial::Contains(unsigned int n, unsigned int k)
{
    if (table[n][k] == 0) 
    {
        return false;
    }
    return true;
}

答案 1 :(得分:8)

二项式系数是一个因子除以另外两个因子,尽管底部的k!项以明显的方式取消。

观察如果1009(包括它的倍数)在分子中出现的次数多于分母,则答案mod 1009为0.在分母中出现的次数不能超过分子(因为二项式系数)是整数),因此你必须做任何事情的唯一情况是它们在两者中出现的次数相同。不要忘记将(1009)^ 2的倍数计算为两个,依此类推。

在那之后,我认为你只是在清理小案件(意味着少量的数值乘以/除),虽然我不确定没有几个测试。在正面1009是素数,因此算术模1009在一个场中发生,这意味着在从顶部和底部输出1009的倍数之后,你可以按任何顺序完成乘法和除法mod 1009的其余部分。 / p>

如果还有非小的情况,它们仍然会将长连续的整数相乘。知道1008! (mod 1009)可以简化这一过程。它是-1(如果您愿意,则为1008),因为1 ... 1008是p-1上的素数字段的p非零元素。因此,它们由1,-1和(p-3)/2对乘法反转组成。

因此,例如考虑C((1009 ^ 3),200)的情况。

想象一下,1009的数量是相等的(不知道它们是否相同,因为我没有编写公式来查明),所以这是一个需要工作的情况。

在顶部我们有201 ... 1008,我们必须在预先计算的表中计算或查找,然后是1009,然后是1010 ...... 2017,2018,2019 ... 3026,3027等......范围都是-1,所以我们只需要知道有多少这样的范围。

那留下1009,2018,3027,一旦我们用1009从底部取消它们将只是1,2,3,...... 1008,1010,......,加上一些1009 ^ 2的倍数,我们再次取消并留下连续整数来增加。

我们可以做一些与底部非常相似的东西来计算产品mod 1009的“1 ... 1009 ^ 3 - 200,其中1009的所有功率被分开”。这使我们在一个主要领域中分裂。 IIRC原则上很棘手,但1009是一个足够小的数字,我们可以管理其中的1000个(测试用例数量的上限)。

当然,当k = 200时,存在巨大的重叠,可以更直接地取消。这就是我所说的小案例和非小案例:我把它当作一个非小案例,事实上我们可以通过计算((1009^3-199) * ... * 1009^3) / 200! <来勉强“蛮力”这个案例/ p>

答案 2 :(得分:5)

我认为你不想计算C(n,k)然后减去mod 1009.最大的一个,C(1e15,5e14)将需要像1e16位~1000TB

此外,执行snakiles循环回答1e15次似乎可能需要一段时间。 您可以使用的是,如果

n = n0 + n1 * p + n2 * p ^ 2 ... + nd * p ^ d

m = m0 + m1 * p + m2 * p ^ 2 ... + md * p ^ d

(其中0 <= mi,ni&lt; p)

然后 C(n,m)= C(n0,m0)* C(n1,m1)* ... * C(nd,nd)mod p

请参阅,例如http://www.cecm.sfu.ca/organics/papers/granville/paper/binomial/html/binomial.html

一种方法是使用pascal的三角形来构建所有C(m,n)的表,其中0 <= m <= n <= 1009。

答案 3 :(得分:2)

用于计算nCk的psudo代码:

result = 1    
for i=1 to min{K,N-K}:
   result *= N-i+1
   result /= i
return result

时间复杂度:O(min {K,N-K})

循环变为from i=1 to min{K,N-K}而不是from i=1 to K,这是正常的,因为

C(k,n) = C(k, n-k)

如果你使用GammaLn函数,你可以更有效地计算这个东西。

nCk = exp(GammaLn(n+1)-GammaLn(k+1)-GammaLn(n-k+1))

GammaLn函数是Gamma function的自然对数。我知道有一种有效的算法来计算GammaLn函数,但该算法根本不是微不足道的。

答案 4 :(得分:-1)

以下代码显示了如何获取给定大小“n”的所有二项式系数。您可以轻松地将其修改为在给定的k处停止以确定nCk。它在计算上非常高效,编码简单,适用于非常大的n和k。

binomial_coefficient = 1
output(binomial_coefficient)
col = 0
n = 5

do while col < n
    binomial_coefficient = binomial_coefficient * (n + 1 - (col + 1)) / (col + 1)
    output(binomial_coefficient)
    col = col + 1
loop

二项式系数的输出因此是:

1
1 *  (5 + 1 - (0 + 1)) / (0 + 1) = 5 
5 *  (5 + 1 - (1 + 1)) / (1 + 1) = 15
15 * (5 + 1 - (2 + 1)) / (2 + 1) = 15 
15 * (5 + 1 - (3 + 1)) / (3 + 1) = 5 
5 *  (5 + 1 - (4 + 1)) / (4 + 1) = 1

我曾经在维基百科上找到过这个公式,但出于某种原因它不再存在:(