Кратчайший путь между двумя целыми числами путем сложения или вычитания - PullRequest
17 голосов
/ 02 сентября 2011

Вот описание этой проблемы:

Вам даны два целых числа a и b. Вы хотите найти кратчайшую последовательность операций, необходимую для преобразования a в b, где на каждом шаге вам разрешается складывать или вычитать 5, 7 или 12.

Например, если вы получили a = -5 и b = 19, кратчайший путь будет

-5 + 12 + 12 = 19

Если бы вам дали 1 и 3, самый короткий путь был бы

1 + 7 - 5 = 2

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

Спасибо!

Ответы [ 6 ]

32 голосов
/ 02 сентября 2011

Давайте начнем с набора интересных наблюдений. Как отметили многие другие, цель состоит в том, чтобы найти некоторую линейную комбинацию 5x + 7y + 12z = b - a с целыми коэффициентами, такую, что | x | + | y ​​| + | z | сводится к минимуму Но между этими тремя числами есть несколько очень интересных связей, которые мы можем использовать:

  1. Если у нас когда-либо будет комбинация 5x + 7y + 12z, где x и y оба положительные или оба отрицательные, мы можем отменить некоторое количество x и y, чтобы добавить эквивалентное число 12. Другими словами, ни одно оптимальное решение не имеет одинакового знака на x и y, потому что мы всегда можем сделать это решение лучше.
  2. Если у нас когда-либо будет комбинация 5x + 7y + 12z, где y и z имеют противоположные знаки, мы всегда можем удалить 7 и 12 и добавить 5 соответствующих знаков, чтобы получить лучшее решение. Аналогично, если x и z имеют противоположные знаки, мы всегда можем удалить 5 и 12 и добавить 7 соответствующего знака. Это означает, что нам никогда не нужно рассматривать какое-либо решение, где z имеет тот же знак, что и x или y, потому что это означает, что должно быть лучшее решение.

Давайте подумаем о том, что (1) и (2) сообщают нам все вместе. (1) говорит, что знаки x и y не могут быть одинаковыми, так как мы всегда можем добиться большего. (2) говорит, что если x и z имеют противоположные знаки или если y и z имеют противоположные знаки, мы всегда можем добиться большего. В совокупности это означает, что

Лемма : По крайней мере один из x, y или z должен быть равен нулю в оптимальном решении.

Чтобы увидеть это, если все три отличны от нуля, если x и y имеют один и тот же знак, мы можем явно улучшить решение, заменив их на 12. В противном случае x и y имеют противоположные знаки. Таким образом, если x и z имеют разные знаки, с помощью (2) мы можем заменить их на меньшее число 7, в противном случае y и z имеют разные знаки, а с помощью (2) мы можем заменить их на меньшее число 5.

Хорошо, это выглядит действительно здорово! Это означает, что нам просто нужно решить эти три целочисленных уравнения и найти, какое из них имеет наименьшую сумму коэффициентов:

  • 5x + 7y = b - a
  • 5x + 12z = b - a
  • 7y + 12z = b - a

К счастью, по тождеству Безу , поскольку gcd (5, 7) = gcd (5, 12) = gcd (7, 12) = 1, все эти системы уравнений имеют решение для любого значение b - a.

Теперь посмотрим, как решить каждое из этих уравнений. К счастью, мы можем использовать несколько милых трюков, чтобы значительно сократить пространство поиска. Например, для 5x + 7y = b - a значение x не может быть вне [-6, +6], так как если бы мы могли просто заменить семь из 5 на пять 7. Это означает, что мы можем решить вышеприведенное уравнение, выполнив следующее:

Для x = от -6 до +6 посмотрите, имеет ли 5x + 7y = b - a целочисленное решение, вычислив (b - a) - 5x и посмотрев, делится ли оно на семь. Если это так, то количество шагов, необходимых для решения проблемы, определяется как | x | + | ((б - а) - 5х) / 7 |.

Мы можем использовать аналогичные приемы для решения последних двух уравнений - для второго уравнения x находится в диапазоне от -11 до +11, а для третьего y также в диапазоне от -11 до +11. Затем мы можем просто взять лучший ответ из всех трех уравнений, чтобы увидеть, каков ответ.

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

Let best = infinity

# Solve 5x + 7y = b - a
for x = -6 to +6:
    if ((b - a) - 5 * x) mod 7 = 0:
        best = min(best, |x| + |((b - a) - 5 * x) / 7|)

# Solve 5x + 12y = b - a
for x = -11 to +11:
    if ((b - a) - 5 * x) mod 12 = 0:
        best = min(best, |x| + |((b - a) - 5 * x) / 12|)

# Solve 7x + 12y = b - a
for x = -11 to +11:
    if ((b - a) - 7 * x) mod 12 = 0:
        best = min(best, |x| + |((b - a) - 7 * x) / 12|)

return best;

Этот алгоритм удивительно быстр - он выполняется за O (1) времени, потому что число итераций, необходимое для решения каждой из трех линейных систем, является постоянным (не более 23). Для хранения возможных значений требуется только O (1) памяти, и я думаю, что на практике это, вероятно, самый быстрый алгоритм, который вы сможете написать.

Надеюсь, это поможет!

2 голосов
/ 02 сентября 2011

Вам нужно решить 5x + 7y + 12z = ba.такой, что | x |+ | y ​​|+ | z |это минимум.Возможно, есть прямой математический путь.Возможно, это поможет: http://mathforum.org/library/drmath/view/66870.html

2 голосов
/ 02 сентября 2011

Проблема эквивалентна получению числа a-b. Или abs(a-b), поскольку он симметричен относительно нуля. Я думаю, что это можно сделать легко с принятием динамического программирования . Например, самый быстрый способ получить 23 - это самый быстрый способ получить 23+5,23-5,23+7,23-7,23+12 или 23-12 плюс одну операцию. Если вы примените это пару раз в начальных условиях (стоимость + 5, -5, .. равно 1, остальные бесконечны), вы получите ответ в O(a-b).

0 голосов
/ 06 сентября 2011

Предварительно вычислите операции, необходимые для первого минимального диапазона, после этого просто продолжайте добавлять кратные +12.

0 голосов
/ 02 сентября 2011

Предварительно вычислите все ваши комбинации операций в хэш-карту, а затем просто найдите ответ.

Шаг предварительного расчета займет время, но после его завершения вы найдете ответы в предварительно рассчитанном диапазоне, которые представляют собой 1 операцию поиска.

Вот небольшая демонстрация JavaScript:

// maximum depth of combos to try
var MAX = 6;
// possible operations
var ops = ["+-5", "+5", "+-7", "+7", "+-12", "+12"];
// initial hash map of operations->value
var all = {"+5":5, "+-5":-5, "+7":7, "+-7":-7, "+12":12, "+-12":-12};
var allcnt = 6; // count combos *not needed*
// initial hash map of values->operations, plus "0" so we can avoid it
var unique = {"0": "0", "5":"+5", "-5":"+-5", "7":"+7", "-7":"+-7", "12":"+12", "-12":"+-12" };
var ready = false;
// get all useful combinations of ops
function precalc() {
  var items = [];
  for(var p in all) {
     items.push(p);
  }
  for(var p=0; p<items.length; p++) {
    for(var i=0; i<ops.length; i++) {
      var k = items[p] + ops[i];
      var v = eval(k);
      if(unique[v] == null) {
        unique[v] = k;
        all[k] = v;
        allcnt++;
      }
    }
  }
}
// find an answer within the pre-calc'd depth
function find(a, b) {
  if(ready === false) {
    for(var i=0; i<MAX; i++) precalc();
    ready = true;
  }
  return unique[""+Math.abs(a-b)];
}
// test
find(-5,19);
0 голосов
/ 02 сентября 2011

Полагаю, вы могли бы взглянуть на проблему суммы подмножеств для идей.

...