Недавно Александр Муньис опубликовал новую математическую игру, которую назвал «Переверни список целых чисел». Заключается она в следующем.

  • Составьте список разных положительных чисел (например, 10 5 3). Ваша цель — перевернуть список, используя «ходы» двух видов:

  • Разделите одно из чисел на две части, которые в сумме дают целое; например, (10 5 4) может стать (7 3 5 4) или (10 2 3 4).

  • Объедините два соседних числа в их сумму; например, (7 3 5 4) может стать (7 8 4) или (7 3 9).

Нельзя образовывать число, которое больше максимального числа в исходном списке. Например, если мы пытаемся изменить (10 5 4), то (7 5 3 4) может стать (7 8 4), но не может стать (12 3 4), так как 12 больше, чем 10 — максимальное число исходного списка. Также все элементы списка должны оставаться различными; например, (7 5 3 4) не может стать ни (7 5 7), ни (7 2 3 3 4).

Александр спрашивает: какие эффективные алгоритмы или общие стратегии существуют для решения этих задач? Для данного n должен быть некий список, где n — самое большое число, а количество ходов, необходимых для решения головоломки, является максимальным. Как выглядит максимальная последовательность необходимого количества ходов в зависимости от n? Как выглядят самые «сложные» головоломки? Есть ли способ определить это без брутфорса?


Пользователь HackerNews под никнеймом Someone удачно описал физическую модель игры. Возьмите палочки длиной от 1 до n. Поместите некоторое их подмножество подряд в «жёлоб для решения», остальное — в «неиспользуемый жёлоб». Теперь варианты следующие: заменить две соседние палочки в «жёлобе для решения» на одну неиспользуемую палочку той же длины из «неиспользуемого жёлоба» или же заменить одну палочку в «жёлобе для решения» любыми двумя палочками из «неиспользуемого жёлоба».

Сумма элементов списка остается постоянной; следовательно, то же самое происходит с суммой недостающих элементов (палочек в «неиспользуемом жёлобе»).

Этот инвариант полезен в некоторых доказательствах, приведённых ниже; в частности, обратите внимание, что невозможно разделить элемент a, если сумма палочек в «неиспользуемом жёлобе» меньше a.

Если для некого исходного списка сумма неиспользуемых элементов меньше, чем n – 1, то ни n – 1, ни n не могут быть разделены; следовательно, они не могут поменяться местами, а значит, список будет неразрешимым.

В целом кажется целесообразным классифицировать возможные исходные положения по их наибольшему значению n и исходной длине k. Например, в случае с (10 3 5) наибольшее значение равно 10, а длина — 3.

Томаш Рокицки уже провел некоторое исследование и OEIS-тификацию. OEIS A372008 предлагает наихудшее количество ходов, необходимое для решения любого разрешимого списка с наибольшим значением n. Его записи — это максимумы каждой строки треугольника M(n, k), записи которого указывают на наихудшее число ходов для решения любого разрешимого списка с наибольшим значением n и длиной k.

(Записи с суффиксом ? получены от Томаша Рокицки, но не проверялись моим более медленным кодом).

n=1:  0
n=2:  0 -
n=3:  0 - -
n=4:  0 - -  -
n=5:  0 - -  -   -
n=6:  0 6 14  6  -  -
n=7:  0 4 12 24 26  -    -
n=8:  0 4 14 32 64 74    -    -
n=9:  0 4  8 18 66 76   86    -    -
n=10: 0 4  8 14 20 88  124  126   36    -
n=11: 0 4  8 12 16 26   90? 100? 106?   -  -
n=12: 0 4  8 12 16 20?  34? 112? 128? 130? - -
n=13: 0 4  8 10 14
n=14: 0 4  8 12 16
n=15: 0 4  8 10 14
n=16: 0 4  8 12
n=17: 0 4  8 10
n=18: 0 4  8 12
n=19: 0 4  8 10
n=20: 0 4  8

Между тем, количество C(n, k) разрешимых списков с наибольшим значением n и длиной k это:

n=1:  1
n=2:  1  0
n=3:  1  0    0
n=4:  1  0    0     0
n=5:  1  0    0     0      0
n=6:  1  8   26    12      0      0
n=7:  1 12   82   294    244      0        0
n=8:  1 14  126   760   2316   1846        0         0
n=9:  1 16  168  1344   8238  31678    47164         0         0
n=10: 1 18  216  2016  15098  89078   336154    480598      2640         0
n=11: 1 20  270  2880  25200 181308  1039174?  3907420?  5673092?        0  0
n=12: 1 22  330  3960  39600 332582? 2323524? 12999906? 47886678? 67645030? 0 0
n=13: 1 24  396  5280  59400      ?        ?         ?         ?         ?  ? ? 0
n=14: 1 26  468  6864  85800      ?        ?         ?         ?         ?  ? ? ? 0
n=15: 1 28  546  8736 120120      ?        ?         ?         ?         ?  ? ? ? 0 0
n=16: 1 30  630 10920      ?      ?        ?         ?         ?         ?  ? ? ? ? 0 0
n=17: 1 32  720 13440      ?      ?        ?         ?         ?         ?  ? ? ? ? ? ? 0
n=18: 1 34  816 16320      ?      ?        ?         ?         ?         ?  ? ? ? ? ? ? ? 0
n=19: 1 36  918 19584      ?      ?        ?         ?         ?         ?  ? ? ? ? ? ? ? 0 0
n=20: 1 38 1026     ?      ?      ?        ?         ?         ?         ?  ? ? ? ? ? ? ? ? 0 0

Обратите внимание:

  • Общее количество списков (разрешимых или нет) с наибольшим значением n и длиной k составляет

  • Неизменно, C(n, 1) = 1 и M(n, 1) = 0.

  • Для n ≥ 2 у нас есть C(n, n) = 0, так как если исходный список содержит все n возможных чисел, законных ходов нет.

  • Рассмотрим список длиной k = n – 1. Только одно число a < n отсутствует в первоначальном списке; это значит, мы не сможем разделить наибольшее значение n на само себя, лишь манипулировать элементами слева и справа от n. Итак, чтобы список был разрешимым, сумма элементов слева от n должна быть равна сумме элементов справа от n. Таким образом недостающий элемент должен иметь ту же чётность, что и n(n + 1) / 2. Теперь, если n – 1 изначально находится в левой части, нам обязательно нужно разделить его и восстановить в правой части, то есть сумма недостающих чисел должна быть не менее n – 1; но это невозможно, поскольку лишь a является недостающим элементом. Итак, недостающий элемент a должен быть n – 1, и он должен иметь ту же чётность, что и n(n + 1) / 2 , чтобы мы могли разделить остаток поровну между левой и правой частями. Согласно этой логике, C(n, n – 1) заведомо равен нулю для n ∈ {11, 12, 15, 16, 19, 20...}. С другой стороны, он может быть ненулевым, например, C(10, 9) = 2640.

  • Интуитивно кажется правдоподобным, что ∀k∃m∀n > m : C(n, k) = Total(n, k).

  • Для n ≥ 7 у нас есть C(n, 2) = Total(n, 2) и M(n, 2) = 4. Доказательство от пользователя SevenNinths смотрите здесь.

  • Для n ≥ 9 у нас, кажется, есть C(n, 3) = Total(n, 3) и M(n, 3) = 8.

  • Для n ≥ 9 у нас, кажется, есть C(n, 4) = Total(n, 4); но пока M(n, 4) колеблется между 10 и 12. Будет ли оно стабилизироваться до 12 (предполагая, что ∀k∃m∀n > m : M(n, k) = 4(k – 1)), или здесь происходит что-то более сложное?

Пользователь SevenNinths приводит конструктивное доказательство того, что ∀n ≥ 7 : M(n, 2) = 4, в виде безупречного алгоритма, позволяющего перевернуть любой двухэлементный список с n ≥ 7. Интуитивно можно предположить, что надежный алгоритм должен существовать также для списков из 3, 4 и более элементов. Обратите внимание, что алгоритм SevenNinths сохраняет длину всех промежуточных списков равной 4 или меньше, даже для очень больших n. Опять же, интуитивно из этого следует, что должна существовать достаточная длина L(n, k) < n для того, чтобы каждый разрешимый список с наибольшим значением n и исходной длиной k мог быть разрешён без создания списка длиннее чем L(n,k). Предположение для L(n, k) может значительно ускорить решение брутфорсом за счёт сокращения пространства поиска.

Достаточной длиной промежуточного списка L(n, k) для разрешаемых списков с наибольшим значением n и длиной k, является:

n=1:  1
n=2:  1 -
n=3:  1 - -
n=4:  1 - - -
n=5:  1 - - - -
n=6:  1 4 4 4 - -
n=7:  1 4 5 5 5 - -
n=8:  1 4 5 6 7 7 - -
n=9:  1 4 5 7 7 8 8 - -
n=10: 1 4 5 7 8 9 9 9 9 -
n=11: 1 4 5 7 8 9 ? ? ? - -
n=12: 1 4 5 6 8 ? ? ? ? ? - -
n=13: 1 4 5 7 8 ? ? ? ? ? ? ? -
n=14: 1 4 5 6 7 ? ? ? ? ? ? ? ? -
n=15: 1 4 5 7 8 ? ? ? ? ? ? ? ? - -
n=16: 1 4 5 6 ? ? ? ? ? ? ? ? ? ? - -
n=17: 1 4 5 7 ? ? ? ? ? ? ? ? ? ? ? ? -
n=18: 1 4 5 6 ? ? ? ? ? ? ? ? ? ? ? ? ? -
n=19: 1 4 5 7 ? ? ? ? ? ? ? ? ? ? ? ? ? - -
n=20: 1 4 5 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? - -

Код C++17, на котором созданы таблицы, доступен здесь.

Комментарии (9)


  1. CitizenOfDreams
    18.05.2024 13:32
    +11

    Теперь еще и это будут на собеседованиях спрашивать?


  1. Mingun
    18.05.2024 13:32

    Этот инвариант полезен в некоторых доказательствах, приведённых ниже; в частности, обратите внимание, что невозможно разделить элемент a, если сумма палочек в «неиспользуемом жёлобе» не меньше a.

    Ну это явно же неправда. На картинке сумма в «неиспользуемом жёлобе» 4+5 = 9 (и она постоянна), элемент 6 разделяется на 2+4 или 1+5 (2-й и 3-й шаг преобразования на картинке). Утверждение гласит, что 6 нельзя разделить, если 9 не меньше 6: !(9 < 6) == 9 >= 6 == true. Это условие истинно, но элемент разделяется.


    1. mk2
      18.05.2024 13:32

      Косяк перевода. В оригинале:

      in particular, notice that we cannot ever split an element a unless the sum of the sticks in the unused gutter is at least a

      То есть невозможно разделить элемент a, если сумма в «неиспользуемом жёлобе» меньше а. Переводчик запутался в тексте и вставил лишний не, который меняет утверждение на противоположное.


  1. insecto
    18.05.2024 13:32

    Похоже на классическую задачу поиска пути в пространстве состояний.


  1. AndronNSK
    18.05.2024 13:32
    +2

    Удивительно, но я 3 раза прочитал начало статьи, но так и не понял, а чем должен быть переворот.

    Потом открыл оригинал и только там нашёл, в чём же суть задачи всё-таки.


  1. Alexandroppolus
    18.05.2024 13:32
    +1

    Нельзя образовывать число, которое больше максимального числа в исходном списке.

    Также все элементы списка должны оставаться различными

    Интересно, для чего эти условия? Они выглядят искусственными и с ними задача теряет изящность.


    1. Mingun
      18.05.2024 13:32
      +1

      Также все элементы списка должны оставаться различными

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

      Нельзя образовывать число, которое больше максимального числа в исходном списке.

      Опять же, на первом шаге сливаем все числа в одно большое, на втором шаге делим как надо.


      1. DistortNeo
        18.05.2024 13:32

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

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


  1. lightln2
    18.05.2024 13:32
    +1

    Томаш Рокицки уже провел некоторое исследование и OEIS-тификацию

    Тут хочется добавить, что это тот самый Tomas Rokicki, главный гуру в recreational computer science, автор программы Golly (самая известная программа по симуляции игры "жизнь" Конвея);
    также, он доказал, что кубик Рубика собирается за не больше, чем 20 ходов из любой позиции (они использовали мощности Гугла, и в сумме затратили 35 cpu-лет).
    Так что, если такой человек заинтересовался этой задачей, то уже можно считать, что она имеет ненулевое значение в научной и околонаучной тусовке.