3

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.

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;
}
Johnson Steward
  • 534
  • 3
  • 16
  • Do you have to use C++? There are some tricks you can use to avoid overflow, but the simplest solution imo would be to use python.. – matanso Nov 10 '15 at 14:54
  • @matanso you see, *high precision calculation* is one of the examining aspect in OI. Also, if using python, I don't think I can get the 3000ms limit(although it's 6000 for java..) – Johnson Steward Nov 10 '15 at 14:56
  • @JohnsonSteward Python doesn't need to be (much) slower than C++. This highly depends on how the bottleneck is implemented, and it is much more important which algorithm you use. Java is mainly slower because you often have some overhead which you cannot avoid, but this of course depends on many factors. You can not say that C++ is the fastest language, and Java is always slow. – leemes Nov 10 '15 at 15:00
  • How many bits we talkin s a "Big" number (links are great and all but it is best to put all relevant info in the question). If its anywhere in the relm of sanity you can just use http://www.boost.org/doc/libs/1_59_0/libs/multiprecision/doc/html/boost_multiprecision/tut/ints/cpp_int.html and be done with it. – IdeaHat Nov 10 '15 at 15:03
  • You can apply the [modular arithmetic](https://en.wikipedia.org/wiki/Modular_arithmetic) to your intermediate results in oder to avoid overflow. The solution you posted does this with its `mod_pow` function and gets away with using a `long long`. – M Oehm Nov 10 '15 at 15:03
  • @leemes I don't mean that. That's the time limit given by the [problem above](http://acm.hdu.edu.cn/showproblem.php?pid=3037). – Johnson Steward Nov 10 '15 at 15:04
  • @IdeaHat wait a minute, this is an OI problem and I'm not supposed to use any external libraries except for those defined by the C++ standard. – Johnson Steward Nov 10 '15 at 15:06
  • @MOehm didn't get the point. That `mod_pow` is already modularized. And that's why it's called `mod_pow` (note the **mod** there) – Johnson Steward Nov 10 '15 at 15:08
  • Better to post the relevant parts of the post here rather than with links. – chux - Reinstate Monica Nov 10 '15 at 15:10
  • 1
    If you can't use external libs, please add this to the question instead of posting comments only. Also, is there a limit for n,k of the binom.coeff.? As speed is important, this might be good to know for selecting a solution. – deviantfan Nov 10 '15 at 15:23
  • @deviantfan quoted from the problem: 1 <= n, m <= 1000000000, 1 < p < 100000 and p is guaranteed to be a prime. – Johnson Steward Nov 10 '15 at 15:32
  • Please choose one of C and C++. Also, why can't you use Lucas' theorem? – fuz Nov 10 '15 at 15:37
  • @FUZxxl He does use it, but with p up to 100000, it can't work (without modifications) with builtin types (and that's the main problem here). – deviantfan Nov 10 '15 at 15:39
  • @JohnsonSteward Could you please cite the text of the exercise in your question so others can profit from this question even when the link goes down? For me, it already is down and it would definitely be helpful to know your problem before attempting to answer it. – fuz Nov 10 '15 at 15:42
  • @FUZxxl not saying that I ***can't*** use it. Wondered if ways to avoid using it(such big theorem.. Haven't learned it in Maths yet) – Johnson Steward Nov 10 '15 at 15:43
  • 2
    @JohnsonSteward Lucas' theorem is definitely the way to go when you want to compute binomial coefficients modulo a prime number. Lucas' theorem *is* the solution you are looking for. – fuz Nov 10 '15 at 15:45
  • @FUZxxl As you keep talking about Lucas theorem, which the OP mentioned in the question anyways, please tell us how to calculate the binomial coefficients of two numbers in range 100000 efficiently and only with builtin types (because even with the theorem, that's necessary) – deviantfan Nov 10 '15 at 15:56
  • @deviantfan With Lucas' algorithm you can compute binomial coefficients as a product of binomial coefficients with n and m smaller than p and if p² fits into an `unsigned long long`, then products modulo `p` can easily be computed so it's doable. – fuz Nov 10 '15 at 16:13
  • @deviantfan I'm currently writing down the code to do this. Give me a minute. – fuz Nov 10 '15 at 16:19
  • @FUZxxl I understand what you mean, but if it will be fast enough... – deviantfan Nov 10 '15 at 16:20
  • @deviantfan The time complexity of this is O(*p* log_*p* (*m*)). This is better than doing it directly at a runtime of O(*m*) but if *p* is about large, it's still going to be slow. – fuz Nov 10 '15 at 16:31
  • @JohnsonSteward: If you were aware that the arithmetic was modular, then I don't understand your concern about numeric overflow. There were other comments that hinted towards bignum libraries, but they aren't needed, so I posted my comment. – M Oehm Nov 10 '15 at 17:39
  • @MOehm I mean that p will also overflow when the problem is made extreme – Johnson Steward Nov 11 '15 at 06:38

4 Answers4

4

This solution assumes that p2 fits into an 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.

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;
}
fuz
  • 88,405
  • 25
  • 200
  • 352
0

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). Someone else suggested Python, that's a reasonable answer if you know Python. if you want to do it in C++, you can use the int_n type:

#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

and

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.

rts1
  • 1,416
  • 13
  • 15
  • Note that you can't express large constants with int_n, so you HAVE to use strings to express constants. – rts1 Nov 10 '15 at 15:14
  • Still want to say.. that I'm not supposed to use external libraries other than those defined in C++ standard (those which can be directly included in the code and can compile anywhere with simply G++) while solving this sort of problems. – Johnson Steward Nov 10 '15 at 15:16
  • I wish infinite precision ints were part of C++: they should be. ;) – rts1 Nov 10 '15 at 15:20
  • so am I. Then we can save much time from coding (or reciting) our own MP implementations and spend more time thinking about algorithms. – Johnson Steward Nov 10 '15 at 15:24
  • 2
    You can implement a poor man's infinite precision with a vector of ints (like a base 10 number). Then you can implement long multiplication and addition like in high school (with carries and the like). – rts1 Nov 10 '15 at 15:24
  • Another thought is you can factor all the small numbers that need to be multiplied (and you know n choose k is always an int), and then just cross off factors. Whatever is left may fit in a long long. – rts1 Nov 10 '15 at 15:26
  • we used raw arrays for holding bits (like 0-9999 in one element). – Johnson Steward Nov 10 '15 at 15:27
0

You want a bignum (a.k.a. arbitrary precision arithmetic) library.

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.

Then, I would recommend 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 ...). It has an easy C++ interface. It is packaged in most Linux distributions.

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.

Community
  • 1
  • 1
Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
0

Lets suppose that we need to compute a value of (a / b) mod p where p is a prime number. Since p is prime then every number b has an inverse mod p. So (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, (k!)^-1, ((n - k)!)^-1. Total time complexity is O(n).

UPDATE: Here is the code in 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);
}
piotrekg2
  • 1,237
  • 11
  • 21