Как подойти к этому типу проблемы в перестановке и комбинации? - PullRequest
0 голосов
/ 27 июня 2019

Высот

Алиса и Боб отправились в путешествие в горы.Они поднимались и спускались в течение N дней и приходили домой очень уставшими.

Алиса помнит только, что они начали свое путешествие на высоте H1 метров, и они закончили свое путешествие на высоте H2 метров.Боб помнит только, что каждый день они меняли высоту на A, B или C метров.Если их высота в i-й день была x, то их высота в день i + 1 может быть x + A, x + B или x + C.

Теперь, Боб задается вопросом, какмного способов, которыми они могли бы завершить свое путешествие.Два путешествия считаются разными тогда и только тогда, когда существует день, когда высота, на которой Алиса и Боб покрывали этот день во время первого путешествия, отличается от высоты, на которой Алиса и Боб покрывали этот день во время второго путешествия.

Боб спрашиваетАлиса расскажет ей количество способов завершить путешествие.Бобу нужна ваша помощь для решения этой проблемы.

Формат ввода

Первая и единственная строка содержит 6 целых чисел N, H1, H2, A, B, C, который представляет количество дней, в течение которых Алиса и Боб блуждали, высоту, с которой они начали свое путешествие, высоту, на которой они закончили свое путешествие, и три возможных изменения высоты, соответственно.

Выходной формат

Распечатать ответ по модулю 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).

Примечание

Эта проблема изначально возникла в соревновании по взлому , теперь закрыто,Объяснение для ввода и вывода образца было исправлено.

1 Ответ

0 голосов
/ 19 июля 2019

Вот мое решение в Python 3.

Вопрос может быть упрощен с 6 входных параметров до 4 параметров. Нет необходимости в начальных и конечных высотах - разницы достаточно. Кроме того, мы можем изменить ежедневные изменения высоты A, B и C и получить тот же ответ, если мы внесем соответствующее изменение в общее изменение высоты. Например, если мы добавим 1 к каждому из A, B и C, мы можем добавить N к изменению высоты: 1 дополнительный метр каждый день в течение N дней означает 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, что дает число с 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.
...