1

Description:

Given two positive integers N and R, how many different ways are there to cut a rod of length N into R pieces, such that the length of each piece is a positive integer? Output this answer modulo 1,000,000,007.

Example:

With N = 7 and R = 3, there are 15 ways to cut a rod of length 7 into 3 pieces: (1,1,5) , (1,5,1), (5,1,1) , (1,2,4) , (1,4,2) (2,1,4), (2,4,1) , (4,1,2), (4,2,1) , (1,3,3), (3,1,3), (3,3,1), (2,2,3), (2,3,2), (3,2,2).

Constraints:

1 <= R <= N <= 200,000

Testcases:

 N    R       Output
 7    3           15
36    6       324632
81   66    770289477
96   88    550930798

My approach:

I know that the answer is (N-1 choose R-1) mod 1000000007. I have tried all different ways to calculate it, but always 7 out of 10 test cases went time limit exceeded. Here is my code, can anyone tell me what other approach I can use to make it in O(1) time complexity.

from math import factorial

def new(n, r):
    D = factorial(n - 1) // (factorial(r - 1) * factorial(n - r))
    return (D % 1000000007)

if __name__ == '__main__':
    N = [7, 36, 81, 96]
    R = [3, 6, 66, 88]
    answer = [new(n, r) for n,r in zip(N,R)]
    print(answer)
Stef
  • 13,242
  • 2
  • 17
  • 28
  • (Tangentially, code you put in `if __name__ == '__main__’:` should be trivial; the purpose of this boilerplate is to allow you to `import` the code, which you will not want to do anyway if the logic you need is not available via `import`. See also https://stackoverflow.com/a/69778466/874188) – tripleee Dec 15 '21 at 04:53
  • 1
    This is a common challenge, did you search for other solutions to the problem? The usual formulation is simply find a set of R positive integers whose sum is N. – tripleee Dec 15 '21 at 04:55
  • @tripleee Yes I looked everywhere but I couldn't find so I post the question. – aman Kumar mahore Dec 15 '21 at 05:19
  • Can you explain how you partition an interval into subpartitions of negative length? – Alex Reynolds Dec 15 '21 at 05:50
  • I didn't understood what you are asking – aman Kumar mahore Dec 15 '21 at 06:34
  • This sounds like an application of the ["stars and bars" method](https://en.wikipedia.org/wiki/Stars_and_bars_(combinatorics)) – Stef Dec 15 '21 at 08:43
  • 1
    Could you please clarify what counts as different ways to cut the rod? For instance, if N=7 and R=3, I can cut the rod into three pieces of respective lengths 3, 2, 2, or into three pieces of respective lengths 2,3,2, do these two count as two different ways? – Stef Dec 15 '21 at 08:47
  • @Stef If N = 7 and R = 3 then There will be 15 ways to cut a rod of length 7 into 3 pieces, like. (1,1,5) , (1,5,1), (5,1,1) , (1,2,4) , (1,4,2) (2,1,4), (2,4,1) , (4,1,2), (4,2,1) , (1,3,3), (3,1,3), (3,3,1), (2,2,3), (2,3,2), (3,2,2) – aman Kumar mahore Dec 15 '21 at 09:11
  • 1
    @AlexReynolds This might be a language issue: in English, "positive" implies "nonzero". So, "cut the rod into pieces so that the piece lengths are positive integers" really means "cut the rod into pieces so that the piece lengths are integers greater than or equal to 1". The word "positive" here is mostly used to exclude zero-length pieces. Note that the word "nonnegative" is often found in English math texts, with meaning "greater than or equal to 0". – Stef Dec 15 '21 at 09:46
  • yes exactly it means each piece should have length of at least 1 – aman Kumar mahore Dec 15 '21 at 09:59
  • 1
    I have edited the question twice: once for adding your example with N=7 and R=3, and once more to remove all the input/output format specifications, and focus entirely on the actual problem without distractions. Feel free to rollback the edits if you really dislike them. – Stef Dec 15 '21 at 10:55
  • Thanks it does make it simple to understand – aman Kumar mahore Dec 15 '21 at 11:55

3 Answers3

3

I think there's two big optimizations that the problem is looking for you to exploit. The first being to cache intermediate values of factorial() to save computational effort across large batches (large T). The second optimization being to reduce your value mod 1000000007 incrementally, so your numbers stay small, and multiplication stays a constant-time. I've updated the below example to precompute a factorial table using a custom function and itertools.accumulate, instead of merely caching the calls in a recursive implementation (which will eliminate the issues with recursion depth you were seeing).

from itertools import accumulate

MOD_BASE = 1000000007
N_BOUND = 200000

def modmul(m):
    def mul(x, y):
        return x * y % m
    return mul
    
FACTORIALS = [1] + list(accumulate(range(1, N_BOUND+1), modmul(MOD_BASE)))

def nck(n, k, m):
    numerator = FACTORIALS[n]
    denominator = FACTORIALS[k] * FACTORIALS[n-k]
    return numerator * pow(denominator, -1, m) % m

def solve(n, k):
    return nck(n-1, k-1, MOD_BASE)

Running this against the example:

>>> pairs = [(36, 6), (81, 66), (96, 88)]
>>> print([solve(n, k) for n, k in pairs])
[324632, 770289477, 550930798]
Dillon Davis
  • 6,679
  • 2
  • 15
  • 37
  • Brother It's having error of maximum recursion depth reach for any big number like 100,000 – aman Kumar mahore Dec 15 '21 at 09:17
  • @amanKumarmahore That's just because of the factorial function; since it caches results, you can run it in a loop to bypass the recursion. If you run `for k in range(n): factorial(k, 1000000007)` before calling `nck`, there won't be any recursive calls at all. Although at this point, you could just rewrite it in a non-recursive way and explicitly store the results you need. – Stef Dec 15 '21 at 09:21
  • @amanKumarmahore Here is what I meant by "rewrite it in a non-recursive way and explicitly store the resumts you need": [Try it online!](https://tio.run/##jZDdSgQxDIXv@xS5Edq1AxYRZcEnkUE6ux0t3UlLdtYfxGcf005XR1HZXrRNcvLlkPQ6Pka8vEk0TVvXQyKX7nu7GSN5u9tL1BA0DGotgI/V0MEtDB4lZ7EJimv25RgUTc@Ct/f5yxyOzBxEAg8egSw@OGk02HNTuQtxeVasPINh7ruzbc3/xmEImzqR1P1H6jIJTyThNxK58UAIvRB5h7gJP/aWd/LXZosAD4Mjy6VMZXhJbh1GXvVnOrRsgqtNaJdTv3pXkOKzXLRpaEyeUYwXb/u4e3JldrV2hGTPWRzyZS7quVZCJPI4yto5VzRc5UeBEtP0AQ "Python 3.8 (pre-release) – Try It Online") – Stef Dec 15 '21 at 09:32
  • 1
    @amanKumarmahore I've updated the code to precompute factorials instead. It should work fine now. – Dillon Davis Dec 15 '21 at 15:17
2

I literally translated code from accepted answer of Ivaylo Strandjev here and it works much faster:

def get_degree(n, p):# { // returns the degree with which p is in n!
    degree_num = 0
    u = p
    temp = n

    while (u <= temp):
        degree_num += temp // u
        u *= p
    return degree_num

def degree(a, k, p):
    res = 1
    cur = a

    while (k):
        if (k % 2):
            res = (res * cur) % p
        k //= 2
        cur = (cur * cur) % p
    return res


def CNKmodP( n, k, p):
    num_degree = get_degree(n, p) - get_degree(n - k, p)
    den_degree = get_degree(k, p)

    if (num_degree > den_degree):
        return 0

    res = 1
    for i in range(n, n - k, -1):
        ti = i
        while(ti % p == 0):
            ti //= p
        res = (res * ti) % p

    denom = 1
    for i in range(1, k + 1):
        ti = i
        while(ti % p == 0):
            ti //= p
        denom = (denom * ti) % p
    res = (res * degree(denom, p-2, p)) % p
    return res

To apply this approach, you just need to call

result = CNKmodP(n-1, r-1, 1000000007)
MBo
  • 77,366
  • 5
  • 53
  • 86
0

In Java we can use BigInteger because the value of factorials that we calculate may not fit in integer. Additionally BigInteger provides built in methods multiply and divide.

static int CNRmodP(int N, int R, int P) {
    BigInteger ret = BigInteger.ONE;
    for (int i = 0; i < R; i++) {
        ret = ret.multiply(BigInteger.valueOf(N - i))
                .divide(BigInteger.valueOf(i + 1));
    }
    
    BigInteger p = BigInteger.valueOf(P);

    //Calculate Modulus
    BigInteger answer = ret.mod(p);
     
    //Convert BigInteger to integer and return it
    return answer.intValue();
}

To apply the above approach, you just need to call

    result = CNRmodP(N-1, R-1, 1000000007);
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Feb 21 '22 at 13:18