如何在排列和组合中解决这类问题?

时间:2019-06-27 18:53:59

标签: math combinations permutation

海拔

爱丽丝和鲍勃去了山上。他们一直在爬 来回N天,回到家非常累。

爱丽丝只记得他们是在 H1米,他们以H2的海拔高度完成游荡 米。鲍勃只记得他们每天改变高度 ABC米。如果他们在第i天的海拔是x, 那么他们在i + 1日的海拔高度可以是x + Ax + Bx + C

现在,鲍勃(Bob)想知道他们有多少种方式可以完成旅程。 当且仅当存在一天时,两次旅程才被视为不同 当爱丽丝和鲍勃在第一天覆盖当天的高度时 旅程与当天爱丽丝和鲍勃所覆盖的海拔高度不同 第二个旅程。

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

输入格式

第一行和唯一行包含6个整数NH1H2ABC 代表爱丽丝和鲍勃流浪的天数, 他们开始旅行的海拔高度,他们开始的海拔高度 完成了旅程,并且可能发生了三种高度变化, 分别。

输出格式

10**9 + 7为模打印答案。

约束

1 <= N <= 10**5
-10**9 <= H1, H2 <= 10**9
-10**9 <= A, B, C <= 10**9

样本输入

2 0 0 1 0 -1

样本输出

3

说明

只有3种可能的旅程-(0, 0), (1, -1), (-1, 1)

注意

此问题最初来自a hackerearth competition,现已关闭。样本输入和输出的说明已更正。

1 个答案:

答案 0 :(得分:0)

这是我在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的数字,其余为零的情况相同。

我们可以使用数学,尤其是Bezout's identity来查看这是否可行。其他一些数学可以找到实现此目的的所有方法。一旦知道了0,D和E的数量,就可以使用multinomial coefficients查找重新排列这些值的方法。总计所有这些,我们就有答案了。

此代码查找完成旅程的方法总数,并仅在最后以10 ** 9 + 7为模。这是可能的,因为Python使用大整数。我在测试中发现的最大结果是输入值100000 0 100000 0 1 2,在取模之前,得到的数值为47,710位。这在我的机器上花费了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()

这是我针对主要问题的一些测试例程。这些适合pytest。

# 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.