Math 如何在排列组合中处理这类问题? 高度

Math 如何在排列组合中处理这类问题? 高度,math,combinations,permutation,Math,Combinations,Permutation,爱丽丝和鲍勃去山上旅行。他们一直在爬山 上下颠簸了几天,回到家里非常疲惫 爱丽丝只记得他们是在海拔30米的地方开始旅行的 H1米,他们以H2 米。鲍勃只记得他们每天都改变高度 通过A、B或C仪表。如果他们在i第天的高度是x, 然后他们在i+1日的高度可以是x+A,x+B,或x+C 现在,鲍勃想知道他们有多少种方式可以完成他们的旅程。 当且仅当存在一天时,两次旅行被认为是不同的 当艾丽丝和鲍勃在第一次飞行中到达的高度 旅程与艾丽丝和鲍勃当天所处的海拔高度不同 第二次旅行 鲍勃让爱丽丝告诉她完成旅程

爱丽丝和鲍勃去山上旅行。他们一直在爬山 上下颠簸了几天,回到家里非常疲惫

爱丽丝只记得他们是在海拔30米的地方开始旅行的
H1
米,他们以
H2
米。鲍勃只记得他们每天都改变高度 通过
A
B
C
仪表。如果他们在
i
第天的高度是
x
, 然后他们在
i+1
日的高度可以是
x+A
x+B
,或
x+C

现在,鲍勃想知道他们有多少种方式可以完成他们的旅程。 当且仅当存在一天时,两次旅行被认为是不同的 当艾丽丝和鲍勃在第一次飞行中到达的高度 旅程与艾丽丝和鲍勃当天所处的海拔高度不同 第二次旅行

鲍勃让爱丽丝告诉她完成旅程的方法。 鲍勃需要你的帮助来解决这个问题

输入格式 第一行也是唯一一行包含6个整数,
N
H1
H2
A
B
C
,其中 表示Alice和Bob漫游的天数, 他们出发的高度,他们出发的高度 完成了他们的旅程,三次可能的海拔变化, 分别

输出格式 以
10**9+7
为单位打印答案

约束条件 样本输出 解释 只有三种可能的行程--
(0,0),-1),-1),-1,1

笔记
这个问题最初来自,现在已经解决了。示例输入和输出的解释已更正。

这是我在Python 3中的解决方案

该问题可以从6个输入参数简化为4个参数。不需要起始高度和结束高度——两者的差异就足够了。同样,我们可以改变每天的海拔变化A、B和C,如果我们对总海拔变化做相应的改变,我们可以得到相同的答案。例如,如果我们将A、B和C中的每一个加上1,我们可以将N加上海拔变化:N天内每天增加1米意味着总共增加N米。我们可以通过排序使每天的海拔变化“正常化”,使A最小,然后从每个海拔变化中减去A,从总海拔变化中减去N*A。这意味着我们现在需要添加一组0和其他两个值(我们称它们为D和E)。D不大于E

我们现在有一个更简单的问题:取N个值,每个值都是0、D或E,所以它们加起来就是一个特定的总数(比如H)。这与使用最多N个等于D或E的数字以及剩余的零是一样的

特别是,我们可以用数学来看看这是否可行。更多的数学可以找到实现这一点的所有方法。一旦我们知道了有多少个0、D和E,我们就可以用它来找到这些值可以重新排列的方式。把这些加起来,我们就知道答案了

此代码查找完成旅程的总方式数,并仅在最后取模10**9+7。这是可能的,因为Python使用大整数。我在测试中发现的最大结果是输入值
100000 0 100000 0 1 2
,它在取模之前产生一个47710位的数字。这在我的机器上需要8秒多一点

这段代码比需要的要长一点,因为我使一些例程比这个问题需要的更一般。我这样做是为了在其他问题中使用它们。为了清晰起见,我使用了许多注释

# Combinatorial routines -----------------------------------------------


def comb(n, k):
    """Compute the number of ways to choose k elements out of a pile of
    n, ignoring the order of the elements. This is also called
    combinations, or the binomial coefficient of n over k.
    """
    if k < 0 or k > n:
        return 0
    result = 1
    for i in range(min(k, n - k)):
        result = result * (n - i) // (i + 1)
    return result


def multcoeff(*args):
    """Return the multinomial coefficient
    (n1 + n2 + ...)! / n1! / n2! / ..."""
    if not args:  # no parameters
        return 1
    # Find and store the index of the largest parameter so we can skip
    #   it (for efficiency)
    skipndx = args.index(max(args))
    newargs = args[:skipndx] + args[skipndx + 1:]

    result = 1
    num = args[skipndx] + 1  # a factor in the numerator
    for n in newargs:
        for den in range(1, n + 1):  # a factor in the denominator
            result = result * num // den
            num += 1
    return result


def new_multcoeff(prev_multcoeff, x, y, z, ag, bg):
    """Given a multinomial coefficient prev_multcoeff = 
    multcoeff(x-bg, y+ag, z+(bg-ag)), calculate multcoeff(x, y, z)).

    NOTES:  1.  This uses bg multiplications and bg divisions,
                faster than doing multcoeff from scratch.
    """
    result = prev_multcoeff
    for d in range(1, ag + 1):
        result *= y + d
    for d in range(1, bg - ag + 1):
        result *= z + d
    for d in range(bg):
        result //= x - d
    return result


# Number theory routines -----------------------------------------------


def bezout(a, b):
    """For integers a and b, find an integral solution to
    a*x + b*y = gcd(a, b).

    RETURNS:    (x, y, gcd)

    NOTES:  1.  This routine uses the convergents of the continued
                fraction expansion of b / a, so it will be slightly
                faster if a <= b, i.e. the parameters are sorted.
            2.  This routine ensures the gcd is nonnegative.
            3.  If a and/or b is zero, the corresponding x or y
                will also be zero.
            4.  This routine is named after Bezout's identity, which
                guarantees the existences of the solution x, y.
    """
    if not a:
        return (0, (b > 0) - (b < 0), abs(b))  # 2nd is sign(b)
    p1, p = 0, 1  # numerators of the two previous convergents
    q1, q = 1, 0  # denominators of the two previous convergents
    negate_y = True  # flag if negate y=q (True) or x=p (False)
    quotient, remainder = divmod(b, a)
    while remainder:
        b, a = a, remainder
        p, p1 = p * quotient + p1, p
        q, q1 = q * quotient + q1, q
        negate_y = not negate_y
        quotient, remainder = divmod(b, a)
    if a < 0:
        p, q, a = -p, -q, -a  # ensure the gcd is nonnegative
    return (p, -q, a) if negate_y else (-p, q, a)


def byzantine_bball(a, b, s):
    """For nonnegative integers a, b, s, return information about
    integer solutions x, y to a*x + b*y = s. This is
    equivalent to finding a multiset containing only a and b that
    sums to s. The name comes from getting a given basketball score
    given scores for shots and free throws in a hypothetical game of
    "byzantine basketball."

    RETURNS:    None if there is no solution, or an 8-tuple containing
                x   the smallest possible nonnegative integer value of
                    x.
                y   the value of y corresponding to the smallest
                    possible integral value of x. If this is negative,
                    there is no solution for nonnegative x, y.
                g   the greatest common divisor (gcd) of a, b.
                u   the found solution to a*u + b*v = g
                v   "   "
                ag  a // g, or zero if g=0
                bg  b // g, or zero if g=0
                sg  s // g, or zero if g=0

    NOTES:  1.  If a and b are not both zero and one solution x, y is
                returned, then all integer solutions are given by
                x + t * bg, y - t * ag for any integer t.
            2.  This routine is slightly optimized for a <= b. In that
                case, the solution returned also has the smallest sum
                x + y among positive integer solutions.

    """
    # Handle edge cases of zero parameter(s).
    if 0 == a == b:  # the only score possible from 0, 0 is 0
        return (0, 0, 0, 0, 0, 0, 0, 0) if s == 0 else None
    if a == 0:
        sb = s // b
        return (0, sb, b, 0, 1, 0, 1, sb) if s % b == 0 else None
    if b == 0:
        sa = s // a
        return (sa, 0, a, 1, 0, 1, 0, sa) if s % a == 0 else None
    # Find if the score is possible, ignoring the signs of x and y.
    u, v, g = bezout(a, b)
    if s % g:
        return None  # only multiples of the gcd are possible scores
    # Find one way to get the score, ignoring the signs of x and y.
    ag, bg, sg = a // g, b // g, s // g  # we now have ag*u + bg*v = 1
    x, y = sg * u, sg * v  # we now have a*x + b*y = s
    # Find the solution where x is nonnegative and as small as possible.
    t = x // bg  # Python rounds toward minus infinity--what we want
    x, y = x - t * bg, y + t * ag
    # Return the information
    return (x, y, g, u, v, ag, bg, sg)


# Routines for this puzzle ---------------------------------------------


def altitude_reduced(n, h, d, e):
    """Return the number of distinct n-tuples containing only the
    values 0, d, and e that sum to h. Assume that all these
    numbers are integers and that 0 <= d <= e.
    """
    # Handle some impossible special cases
    if n < 0 or h < 0:
        return 0
    # Handle some other simple cases with zero values
    if n == 0:
        return 0 if h else 1
    if 0 == d == e:  # all step values are zero
        return 0 if h else 1
    if 0 == d or d == e:  # e is the only non-zero step value
        # If possible, return # of tuples with proper # of e's, the rest 0's
        return 0 if h % e else comb(n, h // e)
    # Handle the main case 0 < d < e
    # --Try to get the solution with the fewest possible non-zero days:
    #   x d's and y e's and the rest zeros: all solutions are given by
    #   x + t * bg, y - t * ag
    solutions_info = byzantine_bball(d, e, h)
    if not solutions_info:
        return 0  # no way at all to get h from  d, e
    x, y, _, _, _, ag, bg, _ = solutions_info
    # --Loop over all solutions with nonnegative x, y, small enough x + y
    result = 0
    while y >= 0 and x + y <= n:  # at most n non-zero days
        # Find multcoeff(x, y, n - x - y), in a faster way
        if result == 0:  # 1st time through loop: no prev coeff available
            amultcoeff = multcoeff(x, y, n - x - y)
        else:  # use previous multinomial coefficient
            amultcoeff = new_multcoeff(amultcoeff, x, y, n - x - y, ag, bg)
        result += amultcoeff
        x, y = x + bg, y - ag  # x+y increases by bg-ag >= 0
    return result


def altitudes(input_str=None):
    # Get the input
    if input_str is None:
        input_str = input('Numbers N H1 H2 A B C? ')
    # input_str = '100000 0 100000 0 1 2'  # replace with prev line for input
    n, h1, h2, a, b, c = map(int, input_str.strip().split())

    # Reduce the number of parameters by normalizing the values
    h_diff = h2 - h1  # net altitude change
    a, b, c = sorted((a, b, c))  # a is now the smallest
    h, d, e = h_diff - n * a, b - a, c - a  # reduce a to zero

    # Solve the reduced problem
    print(altitude_reduced(n, h, d, e) % (10**9 + 7))


if __name__ == '__main__':
    altitudes()
组合例程----------------------------------------------- def梳(n,k): “”“计算从一堆元素中选择k个元素的方法数。” n、 忽略元素的顺序。这也称为 组合,或n对k的二项式系数。 """ 如果k<0或k>n: 返回0 结果=1 对于范围内的i(min(k,n-k)): 结果=结果*(n-i)/(i+1) 返回结果 def multcoeff(*参数): “”“返回多项式系数。” (n1+n2+…)!/n1!/n2!/…“”“ 如果没有参数:#没有参数 返回1 #查找并存储最大参数的索引,以便跳过 #it(为了效率) skipndx=参数索引(最大值(参数)) newargs=args[:skipndx]+args[skipndx+1:] 结果=1 num=args[skipndx]+1#分子中的一个因子 对于newargs中的n: 对于范围(1,n+1)内的den:#分母中的一个因子 结果=结果*num//den num+=1 返回结果 def新multcoeff(上一个multcoeff,x,y,z,ag,bg): “”“给定多项式系数prev_MultCoff= multcoeff(x-bg,y+ag,z+(bg-ag)),计算multcoeff(x,y,z))。 注:1.使用bg乘法和bg除法, 比从头开始执行multceff更快。 """ 结果=prev_multcoeff 对于范围(1,ag+1)内的d: 结果*=y+d 对于范围(1,bg-ag+1)内的d: 结果*=z+d 对于范围内的d(背景): 结果/=x-d 返回结果 #数论例程----------------------------------------------- def bezout(a、b): “”“对于整数a和b,找到 a*x+b*y=gcd(a,b)。 返回:(x,y,gcd) 注:1.此例程使用连续函数的收敛性 b/a的分数膨胀,所以它会稍微
如果欢迎使用StackOverflow,则速度会更快。此问题缺少上下文或其他详细信息:请通过提供其他上下文来改进此问题,其中最好包括您对问题的想法以及您为解决此问题所做的任何尝试,包括代码尝试。此信息可帮助其他人确定您在哪些方面遇到了问题
2 0 0 1 0 -1
3
# Combinatorial routines -----------------------------------------------


def comb(n, k):
    """Compute the number of ways to choose k elements out of a pile of
    n, ignoring the order of the elements. This is also called
    combinations, or the binomial coefficient of n over k.
    """
    if k < 0 or k > n:
        return 0
    result = 1
    for i in range(min(k, n - k)):
        result = result * (n - i) // (i + 1)
    return result


def multcoeff(*args):
    """Return the multinomial coefficient
    (n1 + n2 + ...)! / n1! / n2! / ..."""
    if not args:  # no parameters
        return 1
    # Find and store the index of the largest parameter so we can skip
    #   it (for efficiency)
    skipndx = args.index(max(args))
    newargs = args[:skipndx] + args[skipndx + 1:]

    result = 1
    num = args[skipndx] + 1  # a factor in the numerator
    for n in newargs:
        for den in range(1, n + 1):  # a factor in the denominator
            result = result * num // den
            num += 1
    return result


def new_multcoeff(prev_multcoeff, x, y, z, ag, bg):
    """Given a multinomial coefficient prev_multcoeff = 
    multcoeff(x-bg, y+ag, z+(bg-ag)), calculate multcoeff(x, y, z)).

    NOTES:  1.  This uses bg multiplications and bg divisions,
                faster than doing multcoeff from scratch.
    """
    result = prev_multcoeff
    for d in range(1, ag + 1):
        result *= y + d
    for d in range(1, bg - ag + 1):
        result *= z + d
    for d in range(bg):
        result //= x - d
    return result


# Number theory routines -----------------------------------------------


def bezout(a, b):
    """For integers a and b, find an integral solution to
    a*x + b*y = gcd(a, b).

    RETURNS:    (x, y, gcd)

    NOTES:  1.  This routine uses the convergents of the continued
                fraction expansion of b / a, so it will be slightly
                faster if a <= b, i.e. the parameters are sorted.
            2.  This routine ensures the gcd is nonnegative.
            3.  If a and/or b is zero, the corresponding x or y
                will also be zero.
            4.  This routine is named after Bezout's identity, which
                guarantees the existences of the solution x, y.
    """
    if not a:
        return (0, (b > 0) - (b < 0), abs(b))  # 2nd is sign(b)
    p1, p = 0, 1  # numerators of the two previous convergents
    q1, q = 1, 0  # denominators of the two previous convergents
    negate_y = True  # flag if negate y=q (True) or x=p (False)
    quotient, remainder = divmod(b, a)
    while remainder:
        b, a = a, remainder
        p, p1 = p * quotient + p1, p
        q, q1 = q * quotient + q1, q
        negate_y = not negate_y
        quotient, remainder = divmod(b, a)
    if a < 0:
        p, q, a = -p, -q, -a  # ensure the gcd is nonnegative
    return (p, -q, a) if negate_y else (-p, q, a)


def byzantine_bball(a, b, s):
    """For nonnegative integers a, b, s, return information about
    integer solutions x, y to a*x + b*y = s. This is
    equivalent to finding a multiset containing only a and b that
    sums to s. The name comes from getting a given basketball score
    given scores for shots and free throws in a hypothetical game of
    "byzantine basketball."

    RETURNS:    None if there is no solution, or an 8-tuple containing
                x   the smallest possible nonnegative integer value of
                    x.
                y   the value of y corresponding to the smallest
                    possible integral value of x. If this is negative,
                    there is no solution for nonnegative x, y.
                g   the greatest common divisor (gcd) of a, b.
                u   the found solution to a*u + b*v = g
                v   "   "
                ag  a // g, or zero if g=0
                bg  b // g, or zero if g=0
                sg  s // g, or zero if g=0

    NOTES:  1.  If a and b are not both zero and one solution x, y is
                returned, then all integer solutions are given by
                x + t * bg, y - t * ag for any integer t.
            2.  This routine is slightly optimized for a <= b. In that
                case, the solution returned also has the smallest sum
                x + y among positive integer solutions.

    """
    # Handle edge cases of zero parameter(s).
    if 0 == a == b:  # the only score possible from 0, 0 is 0
        return (0, 0, 0, 0, 0, 0, 0, 0) if s == 0 else None
    if a == 0:
        sb = s // b
        return (0, sb, b, 0, 1, 0, 1, sb) if s % b == 0 else None
    if b == 0:
        sa = s // a
        return (sa, 0, a, 1, 0, 1, 0, sa) if s % a == 0 else None
    # Find if the score is possible, ignoring the signs of x and y.
    u, v, g = bezout(a, b)
    if s % g:
        return None  # only multiples of the gcd are possible scores
    # Find one way to get the score, ignoring the signs of x and y.
    ag, bg, sg = a // g, b // g, s // g  # we now have ag*u + bg*v = 1
    x, y = sg * u, sg * v  # we now have a*x + b*y = s
    # Find the solution where x is nonnegative and as small as possible.
    t = x // bg  # Python rounds toward minus infinity--what we want
    x, y = x - t * bg, y + t * ag
    # Return the information
    return (x, y, g, u, v, ag, bg, sg)


# Routines for this puzzle ---------------------------------------------


def altitude_reduced(n, h, d, e):
    """Return the number of distinct n-tuples containing only the
    values 0, d, and e that sum to h. Assume that all these
    numbers are integers and that 0 <= d <= e.
    """
    # Handle some impossible special cases
    if n < 0 or h < 0:
        return 0
    # Handle some other simple cases with zero values
    if n == 0:
        return 0 if h else 1
    if 0 == d == e:  # all step values are zero
        return 0 if h else 1
    if 0 == d or d == e:  # e is the only non-zero step value
        # If possible, return # of tuples with proper # of e's, the rest 0's
        return 0 if h % e else comb(n, h // e)
    # Handle the main case 0 < d < e
    # --Try to get the solution with the fewest possible non-zero days:
    #   x d's and y e's and the rest zeros: all solutions are given by
    #   x + t * bg, y - t * ag
    solutions_info = byzantine_bball(d, e, h)
    if not solutions_info:
        return 0  # no way at all to get h from  d, e
    x, y, _, _, _, ag, bg, _ = solutions_info
    # --Loop over all solutions with nonnegative x, y, small enough x + y
    result = 0
    while y >= 0 and x + y <= n:  # at most n non-zero days
        # Find multcoeff(x, y, n - x - y), in a faster way
        if result == 0:  # 1st time through loop: no prev coeff available
            amultcoeff = multcoeff(x, y, n - x - y)
        else:  # use previous multinomial coefficient
            amultcoeff = new_multcoeff(amultcoeff, x, y, n - x - y, ag, bg)
        result += amultcoeff
        x, y = x + bg, y - ag  # x+y increases by bg-ag >= 0
    return result


def altitudes(input_str=None):
    # Get the input
    if input_str is None:
        input_str = input('Numbers N H1 H2 A B C? ')
    # input_str = '100000 0 100000 0 1 2'  # replace with prev line for input
    n, h1, h2, a, b, c = map(int, input_str.strip().split())

    # Reduce the number of parameters by normalizing the values
    h_diff = h2 - h1  # net altitude change
    a, b, c = sorted((a, b, c))  # a is now the smallest
    h, d, e = h_diff - n * a, b - a, c - a  # reduce a to zero

    # Solve the reduced problem
    print(altitude_reduced(n, h, d, e) % (10**9 + 7))


if __name__ == '__main__':
    altitudes()
# Testing, some with pytest ---------------------------------------------------

import itertools  # for testing
import collections  # for testing


def brute(n, h, d, e):
    """Do alt_reduced with brute force."""
    return sum(1 for v in itertools.product({0, d, e}, repeat=n)
               if sum(v) == h)


def brute_count(n, d, e):
    """Count achieved heights with brute force."""
    if n < 0:
        return collections.Counter()
    return collections.Counter(
        sum(v) for v in itertools.product({0, d, e}, repeat=n)
    )


def test_impossible():
    assert altitude_reduced(0, 6, 1, 2) == 0
    assert altitude_reduced(-1, 6, 1, 2) == 0
    assert altitude_reduced(3, -1, 1, 2) == 0


def test_simple():
    assert altitude_reduced(1, 0, 0, 0) == 1
    assert altitude_reduced(1, 1, 0, 0) == 0
    assert altitude_reduced(1, -1, 0, 0) == 0
    assert altitude_reduced(1, 1, 0, 1) == 1
    assert altitude_reduced(1, 1, 1, 1) == 1
    assert altitude_reduced(1, 2, 0, 1) == 0
    assert altitude_reduced(1, 2, 1, 1) == 0
    assert altitude_reduced(2, 4, 0, 3) == 0
    assert altitude_reduced(2, 4, 3, 3) == 0
    assert altitude_reduced(2, 4, 0, 2) == 1
    assert altitude_reduced(2, 4, 2, 2) == 1
    assert altitude_reduced(3, 4, 0, 2) == 3
    assert altitude_reduced(3, 4, 2, 2) == 3
    assert altitude_reduced(4, 4, 0, 2) == 6
    assert altitude_reduced(4, 4, 2, 2) == 6
    assert altitude_reduced(2, 6, 0, 2) == 0
    assert altitude_reduced(2, 6, 2, 2) == 0


def test_main():
    N = 12
    maxcnt = 0
    for n in range(-1, N):
        for d in range(N):  # must have 0 <= d
            for e in range(d, N):  # must have d <= e
                counts = brute_count(n, d, e)
                for h, cnt in counts.items():
                    if cnt == 25653:
                        print(n, h, d, e, cnt)
                    maxcnt = max(maxcnt, cnt)
                    assert cnt == altitude_reduced(n, h, d, e)
    print(maxcnt)  # got 25653 for N = 12, (n, h, d, e) = (11, 11, 1, 2) etc.