Алгоритм для достижения числа за фиксированное количество шагов, используя только сложение, деление и умножение - PullRequest
13 голосов
/ 16 февраля 2011

Работая над игрой на работе, и в какой-то момент игры игрок попадает в бонусную игру. Количество, которое им нужно, чтобы выиграть, предопределено, однако мы бы хотели придумать алгоритм, который использует сложение, умножение и деление, чтобы получить это количество за x шагов. Количество шагов также будет известно заранее, поэтому алгоритм просто должен будет выяснить, как использовать это количество шагов для достижения числа.

Вы можете использовать только вычисления от +1 до +15, x2, x4, / 2, / 4. Вы можете превысить целевое число во время шагов, но должны в конечном итоге достичь целевого числа на последнем шаге. Количество шагов обычно составляет от 15 до 30, и вы всегда начинаете с 0.

Например: Сумма: 100, Шаги: 10 == +10, +2, x2, +4, x4, +10, / 2, +15, +15, + 9

Сумма: 40, Шаги: 12 == +15, +1, +5, +2, +1, / 2, * 4, +6, +6, / 4, +5, * 2

Мне любопытно, может ли что-то подобное существовать? Я уверен, что мы можем что-то придумать, но я не хотел заново изобретать колесо, если есть общий алгоритм, который справится с этой задачей.


Обновление: внесены некоторые незначительные изменения в код @ FryGuy, чтобы сделать маршрут, по которому нужно достичь целевого числа, несколько случайным. Его решение работало отлично, как есть, но, увидев его работоспособным и приняв во внимание комментарии @Argote и @Moron, я понял, что нужно иметь некоторый уровень рандомизации, чтобы сделать его привлекательным для наших игроков. Добавил +1 к 10 шагам, чтобы достичь целевого числа 10, отлично работает, но «скучно» с точки зрения того, как мы будем его использовать. Большое спасибо всем, кто прокомментировал и ответил.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CR
{
    class Program
    {
        static void Main(string[] args)
        {
            while (true)
            {
                int targetNumber = 20;
                int steps = 13;
                int[] route = null;
                Boolean routeAcceptable = false;

                // Continue choosing routes until we find one that is acceptable (doesn't average above or target win, but does exceed it at least once)
                while(!routeAcceptable)
                {
                    routeAcceptable = CalculateRoute(targetNumber, steps, out route) && route.Average() < targetNumber && route.Max() > targetNumber;
                }

                foreach (int i in route.Reverse())
                {
                    Console.WriteLine(i);
                }
                Console.WriteLine("-----------------------");
                Console.ReadLine();
            }
        }

        static Boolean CalculateRoute(int targetNumber, int numSteps, out int[] route)
        {
            int maxValue = targetNumber * 16;
            bool[,] reachable = new bool[numSteps + 1, maxValue];

            // build up the map
            reachable[0, 0] = true;
            for (int step = 0; step < numSteps; step++)
            {
                for (int n = 0; n < maxValue; n++)
                {
                    if (reachable[step, n])
                    {
                        foreach (int nextNum in ReachableNumbersFrom(n))
                        {
                            if (nextNum < maxValue && nextNum > 0)
                            {
                                reachable[step + 1, nextNum] = true;
                            }
                        }
                    }
                }
            }

            // figure out how we got there
            int[] routeTaken = new int[numSteps + 1];
            int current = targetNumber;
            for (int step = numSteps; step >= 0; step--)
            {
                routeTaken[step] = current;
                bool good = false;

                // Randomize the reachable numbers enumeration to make the route 'interesting'
                foreach (int prev in RandomizedIEnumerbale(ReachableNumbersFromReverse(current)))
                {
                    if (prev < targetNumber * 8)
                    {
                        if (reachable[step, prev])
                        {
                            current = prev;
                            good = true;

                            // Avoid hitting the same number twice, again to make the route 'interesting'
                            for (int c = numSteps; c >= 0; c--)
                            {
                                reachable[c, prev] = false;
                            }
                            break;
                        }
                    }
                }

                if (!good)
                {
                    route = routeTaken;
                    return false;
                }
            }

            route = routeTaken;
            return true;
        }

        static IEnumerable<int> ReachableNumbersFrom(int n)
        {
            // additions
            for (int i = 1; i <= 15; i++)
            {
                yield return n + i;
            }

            // mults/divides
            yield return n / 2;
            yield return n / 4;
            yield return n * 2;
            yield return n * 4;
        }

        static IEnumerable<int> ReachableNumbersFromReverse(int n)
        {
            // additions
            for (int i = 1; i <= 15; i++)
            {
                if (n - i >= 0)
                    yield return n - i;
            }

            // mults/divides
            if (n % 2 == 0)
                yield return n / 2;
            if (n % 4 == 0)
                yield return n / 4;
            yield return n * 2;
            yield return n * 4;
        }

        static IEnumerable<int> RandomizedIEnumerbale(IEnumerable<int> enumerbale)
        {
            Random random = new Random(System.DateTime.Now.Millisecond);
            return (
                from r in
                    (
                        from num in enumerbale
                        select new { Num = num, Order = random.Next() }
                    )
                orderby r.Order
                select r.Num
                );
        }
    }
}

Ответы [ 3 ]

10 голосов
/ 16 февраля 2011

Я бы использовал динамическое программирование.Сначала создайте карту, на которой числа доступны с каждого шага, а затем вернитесь назад, чтобы узнать, как вы могли туда добраться:

void CalculateRoute(int targetNumber, int numSteps)
{
    int maxValue = targetNumber * 16;
    bool[,] reachable = new bool[numSteps + 1, maxValue];

    // build up the map
    reachable[0, 0] = true;
    for (int step = 0; step < numSteps; step++)
    {
        for (int n = 0; n < maxValue; n++)
        {
            if (reachable[step, n])
            {
                foreach (int nextNum in ReachableNumbersFrom(n))
                {
                    if (nextNum < maxValue && nextNum >= 0)
                        reachable[step + 1, nextNum] = true;
                }
            }
        }
    }

    // figure out how we got there
    int current = targetNumber;
    for (int step = numSteps; step >= 0; step--)
    {
        Console.WriteLine(current);

        bool good = false;
        foreach (int prev in ReachableNumbersFromReverse(current))
        {
            if (reachable[step, prev])
            {
                current = prev;
                good = true;
                break;
            }
        }

        if (!good)
        {
            Console.WriteLine("Unable to proceed");
            break;
        }
    }
}

IEnumerable<int> ReachableNumbersFrom(int n)
{
    // additions
    for (int i = 1; i <= 15; i++)
        yield return n + i;

    // mults/divides
    yield return n / 2;
    yield return n / 4;
    yield return n * 2;
    yield return n * 4;
}

IEnumerable<int> ReachableNumbersFromReverse(int n)
{
    // additions
    for (int i = 1; i <= 15; i++)
        yield return n - i;

    // mults/divides
    if (n % 2 == 0)
        yield return n / 2;
    if (n % 4 == 0)
        yield return n / 4;
    yield return n * 2;
    yield return n * 4;
}
4 голосов
/ 16 февраля 2011

работает в обратном направлении от вашего желаемого решения
, когда ваши деление и умножение составляют только 2 и 4, это позволяет легко узнать, когда вы можете или не можете выполнять эти операции
, а затем, за последние 4-5 шагов, вы можете просто сделать это произвольно вернуться к 0

, чтобы добавить к этому;Вы можете использовать случайные операции на начальном этапе, проверяя, что вы не выполняете недопустимые операции, и вы также должны включить проверку диапазона.Вы не хотите получать число наподобие 400, а затем делить его на 4 раза как ваши последние операции, чтобы вернуться к 0.

3 голосов
/ 16 февраля 2011

Вы можете грубо форсировать его с помощью дерева поиска, которое имеет N уровней глубины, где N - количество шагов. Это будет O (m ^ n), где m - количество разрешенных операций.

Вероятно, есть лучший алгоритм, но он должен работать для меньших значений N.

Например, используйте {Ширина, Глубина} -Первый поиск или A *.

...