简体   繁体   English

如何通过质数求极大二项式系数?

[英]How to calculate EXTREMELY big binomial coefficients modulo by prime number?

This problem 's answer turns out to be calculating large binomial coefficients modulo prime number using Lucas' theorem . 这个问题的答案原来是使用卢卡斯定理计算大二项式系数模数。 Here's the solution to that problem using this technique: here . 这是使用该技术解决该问题的方法: 这里

Now my questions are: 现在我的问题是:

  • Seems like my code expires if the data increases due to overflow of variables. 如果数据由于变量溢出而增加,似乎我的代码过期了。 Any ways to handle this? 有什么办法解决吗?
  • Are there any ways to do this without using this theorem ? 有没有不用此定理的方法吗?

EDIT: note that as this is an OI or ACM problem, external libs other than original ones are not permitted. 编辑:请注意,由于这是OI或ACM问题,因此不允许使用除原始库以外的其他库。

Code below: 代码如下:

#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;

#define N 100010

long long mod_pow(int a,int n,int p)
{
    long long ret=1;
    long long A=a;
    while(n)
    {
        if (n & 1)
            ret=(ret*A)%p;
        A=(A*A)%p;
        n>>=1;
    }
    return ret;
}

long long factorial[N];

void init(long long p)
{
    factorial[0] = 1;
    for(int i = 1;i <= p;i++)
        factorial[i] = factorial[i-1]*i%p;
    //for(int i = 0;i < p;i++)
        //ni[i] = mod_pow(factorial[i],p-2,p);
}

long long Lucas(long long a,long long k,long long p) 
{
    long long re = 1;
    while(a && k)
    {
        long long aa = a%p;long long bb = k%p;
        if(aa < bb) return 0; 
        re = re*factorial[aa]*mod_pow(factorial[bb]*factorial[aa-bb]%p,p-2,p)%p;
        a /= p;
        k /= p;
    }
    return re;
}

int main()
{
    int t;
    cin >> t;
    while(t--)
    {
        long long n,m,p;
        cin >> n >> m >> p;
        init(p);
        cout << Lucas(n+m,m,p) << "\n";
    }
    return 0;
}

This solution assumes that p 2 fits into an unsigned long long . 该解决方案假定p 2适合unsigned long long Since an unsigned long long has at least 64 bits as per standard, this works at least for p up to 4 billion, much more than the question specifies. 由于根据标准, unsigned long long至少具有64位,因此至少对40亿个p有效 ,这比问题指定的要多得多。

typedef unsigned long long num;

/* x such that a*x = 1 mod p */
num modinv(num a, num p)
{
    /* implement this one on your own */
    /* you can use the extended Euclidean algorithm */
}

/* n chose m mod p */
/* computed with the theorem of Lucas */
num modbinom(num n, num m, num p)
{
    num i, result, divisor, n_, m_;

    if (m == 0)
        return 1;

    /* check for the likely case that the result is zero */
    if (n < m)
        return 0;

    for (n_ = n, m_ = m; m_ > 0; n_ /= p, m_ /= p)
        if (n_ % p < m_ % p)
            return 0;

    for (result = 1; n >= p || m >= p; n /= p, m /= p) {
        result *= modbinom(n % p, m % p, p);
        result %= p;
    }

    /* avoid unnecessary computations */
    if (m > n - m)
         m = n - m;

    divisor = 1;
    for (i = 0; i < m; i++) {
         result *= n - i;
         result %= p;

         divisor *= i + 1;
         divisor %= p;
    }

    result *= modinv(divisor, p);
    result %= p;

    return result;
}

An infinite precision integer seems like the way to go. 无限精度整数似乎是可行的方法。

If you are in C++, the PicklingTools library has an "infinite precision" integer (similar to Python's LONG type). 如果您使用的是C ++,则PicklingTools库具有一个“无限精度”整数(类似于Python的LONG类型)。 Someone else suggested Python, that's a reasonable answer if you know Python. 有人建议使用Python,如果您了解Python,那是一个合理的答案。 if you want to do it in C++, you can use the int_n type: 如果要在C ++中执行此操作,则可以使用int_n类型:

#include "ocval.h"
int_n n="012345678910227836478627843";
n = n + 1;   // Can combine with other plain ints as well

Take a look at the documentation at: 查看以下位置的文档:

http://www.picklingtools.com/html/usersguide.html#c-int-n-and-the-python-arbitrary-size-ints-long http://www.picklingtools.com/html/usersguide.html#c-int-n-and-the-python-arbitrary-size-ints-long

and

http://www.picklingtools.com/html/faq.html#c-and-otab-tup-int-un-int-n-new-in-picklingtools-1-2-0 http://www.picklingtools.com/html/faq.html#c-and-otab-tup-int-un-int-n-new-in-picklingtools-1-2-0

The download for the C++ PicklingTools is here . C ++ PicklingTools的下载在这里

You want a bignum (aka arbitrary precision arithmetic ) library. 您需要一个bignum (又名任意精度算术 )库。

First, don't write your own bignum (or bigint) library, because efficient algorithms (more efficient than the naive ones you learned at school) are difficult to design and implement. 首先,不要编写自己的bignum(或bigint)库,因为高效的算法(比您在学校学到的天真的算法要高效)很难设计和实现。

Then, I would recommend GMPlib . 然后,我会推荐GMPlib It is free software, well documented, often used, quite efficient, and well designed (with perhaps some imperfections, in particular the inability to plugin your own memory allocator in replacement of the system malloc ; but you probably don't care, unless you want to catch the rare out-of-memory condition ...). 它是自由软件,有充分的文档证明,经常使用的,相当有效的和精心设计的(也许有一些缺陷,特别是无法插入自己的内存分配器来代替系统malloc ;但是您可能不在乎,除非您想捕捉罕见的内存不足情况...)。 It has an easy C++ interface . 它具有简单的C ++接口 It is packaged in most Linux distributions. 它打包在大多数Linux发行版中。

If it is a homework assignment, perhaps your teacher is expecting you to think more on the math, and find, with some proof, a way of solving the problem without any bignums. 如果这是一项家庭作业,则可能是您的老师希望您对数学进行更多思考,并找到一些证据,找到解决问题的方法, 而无需花费太多精力。

Lets suppose that we need to compute a value of (a / b) mod p where p is a prime number. 假设我们需要计算(a / b) mod p的值,其中p是质数。 Since p is prime then every number b has an inverse mod p . 由于p是素数,因此每个数字b都有一个逆模p So (a / b) mod p = (a mod p) * (b mod p)^-1 . 因此(a / b) mod p = (a mod p) * (b mod p)^-1 We can use euclidean algorithm to compute the inverse. 我们可以使用欧几里得算法来计算逆。

To get (n over k) we need to compute n! mod p 要获得(n over k)我们需要计算n! mod p n! mod p , (k!)^-1 , ((n - k)!)^-1 . n! mod p(k!)^-1((n - k)!)^-1 Total time complexity is O(n) . 总时间复杂度为O(n)

UPDATE: Here is the code in c++. 更新:这是c ++中的代码。 I didn't test it extensively though. 我没有对其进行广泛的测试。

int64_t fastPow(int64_t a, int64_t exp, int64_t mod)                               
{                                                                                  
    int64_t res = 1;                                                               
    while (exp)                                                                    
    {                                                                              
        if (exp % 2 == 1)                                                          
        {                                                                          
            res *= a;                                                              
            res %= mod;                                                            
        }                                                                          

        a *= a;                                                                    
        a %= mod;                                                                  
        exp >>= 1;                                                                 
    }                                                                              
    return res;                                                                    
}                                                                                  

// This inverse works only for primes p, it uses Fermat's little theorem                                                                                   
int64_t inverse(int64_t a, int64_t p)                                              
{                                                                                  
    assert(p >= 2);                                                                
    return fastPow(a, p - 2, p);                                                   
}                                                                                  

int64_t binomial(int64_t n, int64_t k, int64_t p)                                  
{                                                                                  
    std::vector<int64_t> fact(n + 1);                                              
    fact[0] = 1;                                                                   
    for (auto i = 1; i <= n; ++i)                                                  
        fact[i] = (fact[i - 1] * i) % p;                                           

    return ((((fact[n] * inverse(fact[k], p)) % p) * inverse(fact[n - k], p)) % p);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM