如何计算(n!)%1000000009

时间:2014-03-11 18:52:39

标签: c++ optimization integer-overflow

我需要找到n!%1000000009。 对于范围为1到20的k,n的类型为2 ^ k。 我正在使用的功能是:

#define llu unsigned long long
#define MOD 1000000009

llu mulmod(llu a,llu b) // This function calculates (a*b)%MOD caring about overflows
{
    llu x=0,y=a%MOD;
    while(b > 0)
    {
        if(b%2 == 1)
        {
            x = (x+y)%MOD;
        }
        y = (y*2)%MOD;
        b /= 2;
    }
    return (x%MOD);
}

llu fun(int n)   // This function returns answer to my query ie. n!%MOD
{
    llu ans=1;
    for(int j=1; j<=n; j++)
    {
        ans=mulmod(ans,j);
    }
    return ans;
}

我的要求是,我需要调用函数'fun',n / 2次。对于15左右的k值,我的代码运行速度太慢。有没有办法更快?

编辑: 实际上我正计算2 * [(i-1)C(2 ^(k-1)-1)] * [((2 ^(k-1))!)^ 2]对于范围2中的所有i ^(k-1)到2 ^ k。我的程序要求(nCr)%MOD关心溢出。

编辑:我需要一种有效的方法来为大n找到nCr%MOD。

2 个答案:

答案 0 :(得分:2)

由于您正在为nCr的多个连续值寻找n,因此您可以使用以下内容:

    (n+1)Cr = (n+1)! / ((r!)*(n+1-r)!)
    (n+1)Cr = n!*(n+1) / ((r!)*(n-r)!*(n+1-r))
    (n+1)Cr = n! / ((r!)*(n-r)!) * (n+1)/(n+1-r)
    (n+1)Cr = nCr * (n+1)/(n+1-r)

这样可以避免显式调用每个i的阶乘函数。

此外,要保存对nCr的第一次调用,您可以使用:

    nC(n-1) = n //where n in your case is 2^(k-1).

修改
正如Aki Suihkonen指出的那样,(a/b) % m != a%m / b%m。所以上面的方法所以上面的方法不会开箱即用。有两种不同的解决方案:

  1. 1000000009是素数,这意味着a/b % m == a*c % m其中cb modulo m的倒数。您可以找到有关如何计算here的说明,并点击Extended Euclidean Algorithm的链接,了解有关如何计算它的更多信息。

  2. 另一个可能更容易的选择是识别出,因为nCr * (n+1)/(n+1-r)必须给出一个整数,所以必须可以将n+1-r == a*b写在a | nCr和{ {1}}(此处b | n+1表示除法,如果您愿意,可以将其重写为|。在不失一般性的情况下,让nCr % a == 0然后让a = gcd(n+1-r,nCr)。这给出了b = (n+1-r) / a。现在你的分区保证是准确的,所以你只需计算它们然后像以前那样继续乘法。 编辑根据评论,我不相信这种方法会工作

  3. 我可能尝试的另一件事是(n+1)Cr == (nCr / a) * ((n+1) / b) % MOD

    llu mulmod(llu a,llu b)

    这也可以节省一些宝贵的时间。

答案 1 :(得分:2)

mulmod例程可以通过较大的因子K来加速。

1)&#39;%&#39;因为(a + b)都小于N,所以是过度的      - 它足以评估c = a+b; if (c>=N) c-=N;
2)可以一次处理多个比特;请参阅&#34; Russian peasant's algorithm&#34;
的优化 3)a * b实际上足够小,可以适应64位无符号长long而不会溢出

由于实际问题是关于 nCr mod M ,因此高级优化需要使用重复

(n+1)Cr mod M = (n+1)nCr / (n+1-r) mod M.

因为公式的左侧(( nCr )mod M )*( n +1)不能被(< em> n + 1- r ),除法需要实现为与模逆的乘法:(n + r-1)^( - 1)。模幂逆 b ^( - 1)是 b ^(M-1), M 是素数。 (否则它 b ^( phi M )),其中 phi 是Euler&#39 ; s Totient功能。)

模幂运算最常用重复平方来实现,在这种情况下,每个除数需要~45次模乘。

如果可以使用重复

nC(r+1) mod M = nCr * (n-r) / (r+1) mod M

只需要计算(r + 1)^(M-1)mod M一次。