【问题标题】:Calculating Binomial Coefficient (nCk) for large n & k计算大 n 和 k 的二项式系数 (nCk)
【发布时间】:2010-08-21 11:00:19
【问题描述】:

我刚看到这个问题,不知道如何解决。你能给我提供算法、C++代码或想法吗?

这是一个非常简单的问题。给定 N 和 K 的值,您需要告诉我们二项式系数 C(N,K) 的值。您可以放心,K

输入的第一行包含测试用例的数量 T,最多 1000 个。接下来的 T 行中的每一行由两个空格分隔的整数 N 和 K 组成,其中 0

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

输入:
3
3 1
5 2
10 3

输出:
3
10
120

【问题讨论】:

  • 这真的是作业吗?这是什么课程?
  • 看起来更像是项目欧拉问题。
  • 它具有这些 SPOJ 问题之一或类似的编程竞赛的形式,他们实际上并不关心您的源代码是什么样的,而只是测试输出。作业问题通常(但不总是)采用“编写一个函数来执行 X”的形式,而不是“编写一个具有基于文本的输入和基于文本的输出的程序”。无论如何,标记器都会想批评您的代码,而不仅仅是告诉您它是否有效,因此它不是一个黑盒程序。
  • 是的,这看起来对家庭作业来说太难了......
  • 我认为问题出在from here

标签: c++ algorithm math combinatorics


【解决方案1】:

注意 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.

因此,您的问题被简化为找到最多 log N/log 1009 个数字(以 1009 为基数的 N 的位数)的乘积模 1009,形式为 a 选择 b,其中 a

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

注意:

对于N=10^15,N选N/2大于 2^(1000000000000000) 这是方式 超出 unsigned long long。

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

这是我很久以前写的一些 Binomial 的代码,你需要做的就是修改它来做模 1009 的操作(可能有错误,不一定推荐的编码风格):

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;
}

【讨论】:

  • 与我对 dmuir 的评论相同,尽管我认为 Wikipedia 文章对此作出了回答:C(2,7) 为 0。
  • @Steve:是的,如果 k 的数字大于 n 的相应数字,则答案为 0。事实上,这是检验二项式系数可被素数整除的标准之一.
  • 是的,这就是当k > n时n选择k的定义。
【解决方案2】:

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

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

在那之后,我认为您只是在清理小案例(意味着要乘/除的值很少),尽管如果没有一些测试我不确定。从好的方面来说,1009 是素数,所以算术模 1009 发生在一个字段中,这意味着在从顶部和底部都取出 1009 的倍数之后,您可以按任何顺序进行其余的乘法和除法模 1009。

如果还存在非小情况,它们仍将涉及将长连续整数相乘。这可以通过了解1008! (mod 1009) 来简化。它是 -1(如果您愿意,可以是 1008),因为 1 ... 1008 是 p 上的素数字段的 p-1 非零元素。因此,它们由 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 的倍数,我们将再次取消它并留下连续的整数来相乘。

我们可以对底部做一些非常相似的事情来计算“1 ... 1009^3 - 200 与 1009 的所有幂相除”的乘积 mod 1009。这给我们留下了一个主要领域的分裂。 IIRC 原则上很棘手,但 1009 是一个足够小的数字,我们可以管理其中的 1000 个(测试用例数量的上限)。

当然,在 k=200 的情况下,会有一个巨大的重叠,可以更直接地取消。这就是我所说的小案例和非小案例:我把它当作一个非小案例来对待,而实际上我们可以通过计算((1009^3-199) * ... * 1009^3) / 200!

【讨论】:

  • 这个案子怎么样? C(1000000000000000,500000000000000) 你的意思并不完全清楚。可以请你写一个伪代码或某事吗?
  • 不是我的作业 ;-) 但我试图用一个例子来解释我的意思。
  • 谢谢!我明白了。这是一个很好的创意算法
  • 谢谢。我希望我没有完全浪费我的时间,卢卡斯定理的证明是从这些方面开始的,计算我提到的所有事情,看到(在一些更狡猾的技巧之后)几乎所有东西都抵消或聚集在一起,并给出了答案。不过,我没有查过任何证据,以防万一我错了,而且我完全错过了一个非常简单的技巧;-)
  • 伟大的纯数学答案!
【解决方案3】:

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

此外,在 snakiles 中执行循环 1e15 次似乎可能需要一段时间。 你可能会使用的是,如果

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

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

(其中 0

那么 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

一种方法是使用帕斯卡三角形为 0

【讨论】:

  • 啊,是的,这直接打断了我对 1008 的所有困扰! = -1。但是,如果 m0 > n0 怎么办?
  • 哎呀,我应该补充一下,就像白痴在他的优秀回答中所做的那样,如果 r,约定是 C(r,s) = 0
【解决方案4】:

计算nCk的伪代码:

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 函数,但该算法一点也不简单。

【讨论】:

  • 考虑到 N 和 K 的可能大小,我会说您的产品很快就会溢出。
  • @snakile:没有更好,因为选择了问题的大小以使天真的方法太慢,并且您遗漏了 mod 1009 部分,如果您要包含它,您会崩溃尝试计算 0/0。
  • @snakile 更好。但是在问题的上下文中,我还注意到您只对结果模 1009 感兴趣。我怀疑我们可以利用它来将自己限制为 O(10^3)​​ 操作而不是 O(10^15) .本能地似乎可能会有一些规则,例如:如果 (N-K) > 2018,结果将始终为 0。但是这些数字是从无处获取的——我必须通过它来检查。
  • 检查结果是否为 0(mod 1009) 很容易。你可以数一下n中有多少个1009!和多少 k!(n-k)!我想了很多的一点是 1009 是一个素数。这没有帮助吗?
  • 哎呀,我不知何故忘记了 mod 1009。这个问题比我想象的要有趣得多。原来我的回答没有用。我认为白痴的答案(使用卢卡斯定理)解决了这个问题,但我不确定。
【解决方案5】:

以下代码显示了如何获取给定大小“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

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

【讨论】:

  • 这将是计算一般二项式系数的一个很好的答案。但是对于这个问题,你会遇到和 snakile 一样的问题:对于大的 n,事情会很快溢出,而你忽略了问题的 1009 部分。
猜你喜欢
  • 2016-05-15
  • 2014-05-22
  • 1970-01-01
  • 2023-04-04
  • 1970-01-01
  • 1970-01-01
  • 2021-12-27
  • 1970-01-01
  • 2016-06-15
相关资源
最近更新 更多