给定一根长度为 N 的杆,你需要将它切成 R 段,使得每段的长度为正,有多少种方法可以做到这一点?

Given a rod of length N , you need to cut it into R pieces , such that each piece's length is positive, how many ways are there to do so?

描述:

给定两个正整数N和R,有多少种不同的方法可以将长度为N的杆切成R段,使得每段的长度都是正整数?输出这个答案模 1,000,000,007.

示例:

N = 7,R = 3,有 15 种方法可以将长度为 7 的杆切成 3 段:(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).

约束条件:

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

测试用例:

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

我的做法:

我知道答案是(N-1 choose R-1) mod 1000000007。我尝试了所有不同的方法来计算它,但 10 个测试用例中总是有 7 个超过了时间限制。这是我的代码,谁能告诉我还有什么其他方法可以在 O(1) 时间复杂度内完成。

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)

我从字面上翻译了 accepted answer of Ivaylo Strandjev here 的代码,它运行得更快:

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

要应用此方法,您只需调用

result = CNKmodP(n-1, r-1, 1000000007)

我认为有两大优化问题需要您加以利用。第一个是缓存 factorial() 的中间值以节省大批量(大 T)的计算量。第二个优化是逐渐减少你的值 mod 1000000007,所以你的数字保持很小,乘法保持恒定时间。我更新了下面的示例,使用自定义函数和 itertools.accumulate 预先计算阶乘 table,而不是仅仅在递归实现中缓存调用(这将消除您所看到的递归深度问题) .

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)

运行这个对照例子:

>>> pairs = [(36, 6), (81, 66), (96, 88)]
>>> print([solve(n, k) for n, k in pairs])
[324632, 770289477, 550930798]

在Java中我们可以使用BigInteger,因为我们计算的阶乘值可能不适合整数。此外,BigInteger 提供了内置方法乘法和除法。

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

要应用上述方法,您只需调用

    result = CNRmodP(N-1, R-1, 1000000007);