Решить головоломку театра BattleBlock

13

Игра BattleBlock Theatre иногда содержит головоломку, которая является обобщенной версией Lights Out . У вас есть три смежных блока, каждый из которых указывает уровень от 1 до 4 включительно с барами, например:

|
||||
||

Если вы дотронетесь до блока, то этот блок, как и любой соседний блок, будет увеличивать свой уровень (переход от 4 до 1). Загадка решается, когда все три блока показывают один и тот же уровень (не имеет значения, какой уровень). Поскольку порядок, в котором вы касаетесь блоков, не имеет значения, мы обозначаем решение по частоте касания каждого блока. Оптимальным решением для вышеуказанного ввода будет 201:

|    --> || --> |||     |||
||||     |      ||      |||
||       ||     ||  --> |||

Игра очень легко обобщает любое количество блоков, хотя для некоторых чисел не все конфигурации разрешимы.

Соревнование

Учитывая последовательность уровней блоков, верните, как часто нужно нажимать на каждый блок, чтобы решить головоломку. Например, приведенный выше пример будет приведен как 142и может дать 201в результате. Если решения не существует, верните какой-либо непротиворечивый вывод по вашему выбору, который отличается от всех потенциальных решений, например, -1пустую строку.

Вы можете написать функцию или программу, получить ввод через STDIN, аргумент командной строки или аргумент функции, в любом удобном формате списка или строки и аналогичным образом вывести через возвращаемое значение или распечатать в STDOUT.

Ваш код должен возвращать правильные результаты для всех тестовых случаев в течение минуты на приемлемой машине. (Это не совсем строгий предел, поэтому, если ваше решение занимает минуту и ​​десять секунд, это нормально, но если это займет 3 минуты, это не так. Хороший алгоритм легко решит их за секунды.)

Это код гольф, поэтому самый короткий ответ (в байтах) выигрывает.

Примеры

Решения не являются уникальными, поэтому вы можете получить разные результаты.

Input                          Output

1                              0
11                             00
12                             No solution
142                            201
434                            101
222                            000
4113                           0230
32444                          No solution
23432                          10301
421232                         212301
3442223221221422412334         0330130000130202221111
22231244334432131322442        No solution
111111111111111111111222       000000000000000000000030
111111111111111111111234       100100100100100100100133
412224131444114441432434       113013201011001101012133

Насколько я знаю, есть ровно 4 решения для каждого входа, где количество блоков равно 0 mod 3 или 1 mod 3, и есть 0 или 16 решений, где это 2 mod 3.

Мартин Эндер
источник
Вам нужно вывести оптимальное решение?
xnor
@xnor Нет, нет.
Мартин Эндер
Должны ли мы печатать ровно одно решение или мы можем распечатать все из них?
Якуб
@Jakube Точно, пожалуйста. Я должен был добавить бонус за все / оптимальное решение, но я не думал об этом достаточно рано, так что любое (одно) решение это.
Мартин Эндер

Ответы:

10

Python 2, 115 байт

n=input()
for F in range(4):
 t=[F];b=0;exec"x=(-n[b]-sum(t[-2:]))%4;t+=x,;b+=1;"*len(n)
 if x<1:print t[:-1];break

Это версия программы для гольфа, которую я написал, обсуждая проблему с Мартином.

Ввод представляет собой список через STDIN. Вывод представляет собой список, представляющий последнее найденное решение, если есть решение, или ноль, если его нет. Например:

>>>
[1, 4, 2]
[2, 1, 1]
>>>
[1, 2]
0
>>>
map(int,"3442223221221422412334")
[2, 3, 3, 2, 1, 3, 2, 0, 0, 2, 1, 3, 2, 2, 0, 0, 2, 2, 3, 1, 1, 3]

Pyth, 32 29 байт

V4J]NVQaJ%_+s>J_2@QN4)I!eJPJB

Обязательный порт. Спасибо @Jakube за сохранение 3 байтов.

Способ ввода такой же, как и выше, попробуйте онлайн .


Объяснение (длинное и полное логики!)

Сначала два основных замечания:

  • Наблюдение 1: Неважно, в каком порядке вы касаетесь блоков

  • Наблюдение 2: Если вы касаетесь блока 4 раза, это равносильно касанию его один раз

Другими словами, если есть решение, то есть решение, в котором число касаний составляет от 0 до 3 включительно.

Поскольку модуль 4 хорош, давайте сделаем это и с блоками. В остальной части этого объяснения уровень блока 0 эквивалентен уровню блока 4.

Теперь давайте обозначим a[k]текущий уровень блока kи x[k]количество раз, которое мы касаемся блока kв решении. Также пусть nбудет общее количество блоков. Как отметил @Jakube, решение должно удовлетворять:

  a[0]   + x[0] + x[1]
= a[1]   + x[0] + x[1] + x[2]
= a[2]          + x[1] + x[2] + x[3]
= a[3]                 + x[2] + x[3] + x[4]
...
= a[n-1]                                     ...  + x[n-2] + x[n-1] + x[n]
= a[n]                                       ...           + x[n-1] + x[n]
= C

где C- конечный уровень, на котором заканчиваются все блоки, от 0 до 3 включительно (помните, что мы рассматриваем уровень 4 как уровень 0), и все приведенные выше уравнения действительно являются конгруэнциями по модулю 4.

Теперь вот самое интересное:

  • Наблюдение 3 : Если решение существует, решение существует для любого конечного уровня блока 0 <= C <= 3.

Есть три случая, основанные на количестве блоков по модулю 3. Объяснение для каждого из них одинаково - для любого количества блоков существует подмножество блоков, которое, если вы прикоснетесь к каждому из них один раз, увеличит все уровни блоков на ровно 1.

0 mod 3 (touch every third block starting from the second):
    .X. / .X. / .X.

1 mod 3 (touch every third block starting from the first):
    X. / .X. / .X. / .X

2 mod 3 (touch every third block starting from either the first or second):
    X. / .X. / .X. / .X.
    .X. / .X. / .X. / .X

Это объясняет, почему существует 4 решения для 0 mod 3и 1 mod 3, и обычно 16 решений для 2 mod 3. Если у вас уже есть решение, прикосновение к блокам, как указано выше, дает другое решение, которое заканчивается на более высоком уровне блока (обтекание).

Так что это значит? Мы можем выбрать любой конечный уровень блока, который Cмы хотим! Давайте выберем C = 0, потому что это экономит на байтах.

Теперь наши уравнения становятся:

0 = a[0] + x[0] + x[1]
0 = a[1] + x[0] + x[1] + x[2]
0 = a[2] + x[1] + x[2] + x[3]
0 = a[3] + x[2] + x[3] + x[4]
...
0 = a[n-1] + x[n-2] + x[n-1] + x[n]
0 = a[n] + x[n-1] + x[n]

И переставить:

x[1] = -a[0] - x[0]
x[2] = -a[1] - x[0] - x[1]
x[3] = -a[2] - x[1] - x[2]
x[4] = -a[3] - x[2] - x[3]
...
x[n] = a[n-1] - x[n-2] - x[n-1]
x[n] = a[n] - x[n-1]

Итак, что мы можем видеть, если мы имеем x[0], то мы можем использовать все уравнения, кроме последнего, чтобы выяснить все остальные x[k]. Последнее уравнение является дополнительным условием, которое мы должны проверить.

Это дает нам алгоритм:

  • Попробуйте все значения для x[0]
  • Используйте приведенные выше уравнения, чтобы решить все другие x[k]
  • Проверьте, выполнено ли последнее условие. Если это так, сохраните решение.

Это дает решение выше.

Так почему же мы иногда не получаем решения 2 mod 3? Давайте снова посмотрим на эти две модели:

X. / .X. / .X. / .X.
.X. / .X. / .X. / .X

Теперь рассмотрим уравнения в этих положениях, т.е. для первого:

0 = a[0] + x[0] + x[1]
0 = a[3] + x[2] + x[3] + x[4]
0 = a[6] + x[5] + x[6] + x[7]
0 = a[9] + x[8] + x[9] + x[10]

Добавьте их:

0 = (a[0] + a[3] + a[6] + a[9]) + (x[0] + x[1] + ... + x[9] + x[10])

Для второго:

0 = a[1] + x[0] + x[1] + x[2]
0 = a[4] + x[3] + x[4] + x[5]
0 = a[7] + x[6] + x[7] + x[8]
0 = a[10] + x[9] + x[10]

Добавьте их снова:

0 = (a[1] + a[4] + a[7] + a[10]) + (x[0] + x[1] + ... + x[9] + x[10])

Так что если (a[1] + a[4] + a[7] + a[10])и (a[0] + a[3] + a[6] + a[9])не равны, то у нас нет решения. Но если они равны, то мы получаем 16 решений. Это было для n = 11случая, но, конечно, это обобщается на любое число, которое 2 mod 3- взять сумму каждого третьего элемента, начиная со второго, и сравнить с суммой каждого третьего элемента, начиная с первого.

Теперь, наконец, возможно ли выяснить, что x[0]должно быть, вместо того, чтобы попробовать все возможности? В конце концов, поскольку мы ограничили наш целевой уровень Cна 0, есть только один, x[0]который дает решение в случае 0 mod 3или в 1 mod 3(как 4 solutions / 4 final levels = 1 solution for a specific final level).

Ответ ... да! Мы можем сделать это для 0 mod 3:

 .X..X
.X..X.

Что переводится как:

0 = a[2] + x[1] + x[2] + x[3]   -> 0 = (a[2] + a[5]) + (x[1] + ... + x[5])
0 = a[5] + x[4] + x[5]          /


0 = a[1] + x[0] + x[1] + x[2]   -> 0 = (a[1] + a[4]) + (x[0] + x[1] + ... + x[5])
0 = a[4] + x[3] + x[4] + x[5]   /

Вычитание дает:

x[1] = (a[2] + a[5]) - (a[1] + a[4])

Точно так же, как 1 mod 3мы можем сделать этот шаблон:

 .X..X.
X..X..X

Который дает:

x[0] = (a[2] + a[5]) - (a[0] + a[3] + a[6])

Они, конечно, обобщаются путем увеличения индексов с шагом 3.

Поскольку 2 mod 3у нас есть два подмножества, которые охватывают каждый блок, мы можем выбрать любой x[0]. Фактически, это верно для x[0], x[1], x[3], x[4], x[6], x[7], ...(в основном, любого индекса, который не соответствует 2 mod 3, поскольку они не охватываются ни одним из подмножеств).

Таким образом, у нас есть способ выбрать x[0]вместо того, чтобы попробовать все возможности ...

... но плохая новость в том, что это не экономит байты (124 байта):

def f(n):s=[];L=len(n);B=sum(n[~-L%3::3])-sum(n[-~L%3::3]);x=A=0;exec"s+=B%4,;A,B=B,-n[x]-A-B;x+=1;"*L*(L%3<2or B<1);print s
Sp3000
источник
Умная. Вы можете сохранить 1 символ, используя Jвместо H2 символов, если вы добавляете последний элемент PJвместо нарезки. <J_1, V4J]NVQaJ%_+s>J_2@QN4)I!eJPJB
Якуб
@Jakube Ах, спасибо. Когда я читал pop, я думал, что это как Python pop, возвращающий последний элемент при удалении из списка. Теперь я вижу, что это не так.
Sp3000
4

Pyth, 72 76 73 66 39 38 символов

Ph+f!eTmu+G%+&H@G_3-@QH@QhH4UtQd^UT2]Y

редактировать 4: понял, что расчетыQ[N]-Q[N+1]+solution[-3] и Q[-2]-Q[-1]+solution[-3]идентичны. Поэтому я пересчитываю решение на 1 и фильтрую решения, где последняя запись равна 0. Затем я выталкиваю последнюю запись. К счастью, особые случаи не требуют дополнительного лечения с таким подходом. -27 символов

редактировать 3: применение некоторых трюков в гольф от FryAmTheEggman: -7 персонаж

редактировать 2: используя фильтр, уменьшить и отобразить: -3 символа

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

Ожидает список целых чисел в качестве входных данных [1,4,2]и выводит правильное решение, [2,0,1]если оно есть, в противном случае - пустой список [].

Объяснение:

Позвольте Qбыть список 5 уровней и Yсписок решения. Следующие уравнения должны выполняться:

  Q0 + Y0 + Y1 
= Q1 + Y0 + Y1 + Y2
= Q2      + Y1 + Y2 + Y3
= Q3           + Y2 + Y3 + Y4
= Q4                + Y3 + Y4

Поэтому, если мы используем любое Y0и Y1, мы можем рассчитать Y2, Y3и Y4следующим образом.

Y2 = (Q0 - Q1     ) mod 4
Y3 = (Q1 - Q2 + Y0) mod 4
Y4 = (Q2 - Q3 + Y1) mod 4

То, что все уровни, кроме последнего, равны (потому что мы не использовали уравнение = Q4 + Y3 + Y4. Чтобы проверить, равен ли этот последний также другим уровням, мы можем просто проверить, если (Q3 - Q4 + Y2) mod 4 == 0. Обратите внимание, что левая часть будет значением Y5Если я вычислю 6-ю часть решения, я могу просто проверить, равен ли он нулю.

В моем подходе я просто перебираю все возможные запуски ( [0,0], to [3,3]), вычисляю длину (input) -1 больше записей и фильтрую все решения, заканчивающиеся нулем.

mu+G%+&H@G_3-@QH@QhH4UtQd^UT2   generates all possible solutions

это в основном следующее:

G = start value           //one of "^UT2", [0,0], [0,1], ..., [9,9]
                          //up to [3,3] would be enough but cost 1 char more
for H in range(len(Q)-1): //"UtQ"
   G+=[(H and G[-3])+(Q(H)-Q(H+1))%4] //"+G%+&H@G_3-@QH@QhH4"
   //H and G[-3] is 0, when H is empty, else G[-3]

затем я фильтрую эти возможные решения для действительных:

f!eT //only use solutions, which end in 0

к этому списку решений я добавляю пустой список, чтобы в нем был хотя бы один элемент

 +....]Y

и возьмите первое решение h, вытолкните последний элемент pи распечатайте его

 Ph

Обратите внимание, что это также работает, если есть только один блок. В моем подходе я получаю стартовую позицию [0,0] и не расширяю ее. Поскольку последняя запись равна 0, она печатает решение [0].

Второй особый случай (2 блока) не такой уж особенный. Не уверен, почему я слишком усложнил вещи раньше.

Jakube
источник
Я думаю, что печать ничего не подходит для решения проблемы, если это единственный случай, когда вы ничего не печатаете. Возможно, придется получить @ MartinBüttner, чтобы подтвердить это
Sp3000
?**lQ]0qhQeQ<lQ3h+f!%-+ePQ@T_3eQ4mu+G]%+&H@G_3-@QH@QhH4UttQd^UT2]Yсоставляет 66 байтов. Производительность немного пострадала, но для меня он все же проходит самый большой тестовый тест <1 с. Пинг мне, если вы хотите объяснения некоторых из гольфов; в этом комментарии недостаточно места;) Надеюсь, вам
понравится использование Pyth
+<list><int>имеет тот же эффект +<list>]<int>, что и вы можете удалить первый ]. Также очень хорошее решение.
Исаак
@isaacg Это то же самое для ~? Похоже, это не было, когда я пытался
Sp3000
@ Sp3000 Просто заменить ~на a- ~<list>]<int>эквивалентно a<list><int>. ~есть +=, а aесть.append()
isaacg
3

Рубин, 320 313 символов

m=gets.chop.chars.map{|x|x.to_i-1}
a=m.map{0}
t=->n{m[n]+=1
m[n-1]+=1if n>0
m[n+1]+=1if n<m.size-1
m.map!{|x|x%4}
a[n]=(a[n]+1)%4}
t[0]until m[0]==1
(2...m.size).map{|n|t[n]until m[n-1]==1}
r=0
while m.uniq.size>1&&m[-1]!=1
(0...m.size).each_with_index{|n,i|([1,3,0][i%3]).times{t[n]}}
(r+=1)>5&&exit
end
$><<a*''

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

Безголовая версия:

#!/usr/bin/ruby

nums = gets.chomp.chars.map {|x| x.to_i-1 }
touches = nums.map {0}

# our goal: make all the numbers 1
# utility function
touch = ->n {
    nums[n] += 1
    nums[n-1] += 1 if n > 0
    nums[n+1] += 1 if n < (nums.length-1)
    nums.map! {|x| x % 4 }
    touches[n] = (touches[n] + 1) % 4
}

# first, start with the very first number
touch[0] until nums[0] == 1

# then, go from index 2 to the end to make the previous index right
(2...nums.length).each {|n|
    touch[n] until nums[n-1] == 1
}

iters = 0
if nums.uniq.length != 1
    # I have no idea why this works
    while nums[-1] != 1
        (0...nums.length).each_with_index {|n, i|
            ([1, 3, 0][i % 3]).times { touch[n] }
        }
        if (iters += 1) > 5
            puts -1
            exit
        end
    end
end

puts touches * ''

Хорошо, это было весело. Вот основной алгоритм, {n}представляющий n «прикосновений» к числу выше n, как показано на одном из примеров:

we want each number to be a 1
first make the first number a 1
3442223221221422412334
2}
1242223221221422412334
 {3} now keep "touch"ing until the number to the left is a 1
1131223221221422412334
  {2}
1113423221221422412334
   {2}
1111243221221422412334
... (repeat this procedure)
1111111111111111111110

Я был немного озадачен здесь. Как я могу превратить 111...1110в серию одинаковых номеров? Таким образом, я сравнил свое решение и правильное решение (обратите внимание: число «касания» на единицу больше, чем должно быть, поскольку вход индексируется 1, а выход 0):

3033233103233301320210
0330130000130202221111

Я заметил, что каждый номер был один от правильного mod 4, поэтому я пометил их с +s, -s и =s:

3033233103233301320210 original program output
+-=+-=+-=+-=+-=+-=+-=+ amount to modify by (+1, -1, or 0 (=))
4334534404534602621511 result (the correct answer)

0330130000130202221111 (the original solution, digits equal to result mod 4)

Это работало некоторое время, пока я не заметил, что иногда конечный результат был 111...11112или 11...1113также! К счастью, многократное применение магической формулы, которая не имеет смысла, но работает, также разобрало их.

Итак, вот оно. Программа, которая начинает иметь смысл, но с течением времени превращается во все более и более безобразное хакерство. Я думаю, что это довольно типичное решение для гольф-кода. :)

Дверная ручка
источник
1
Я люблю последний комментарий в вашем коде :). Вы можете сохранить 2 символа, изменив exit if (r+=1)>5на (r+=1)>5&&exit. Кроме того, (code)while condсинтаксис короче, чем while cond \n code \n end.
Кристиан Лупаску,
2

Python 2, 294 289 285 281 273 байта

n=input();l=len(n);s=[0]*l
for i in range(2,l):
 a=(n[i-2]-n[i-1])%4;s[i]+=a;n[i-1]+=a;n[i]+=a
 if i+1<l:n[i+1]+=a
 n=[a%4for a in n]
if l%3>1 and n!=[n[0]]*l:print"x"
else:
 for i in range(l%3,l-1,3):s[i]+=(n[l-1]-n[l-2])%4
 m=min(s);s=[(a-m)%4 for a in s];print s

DEMO

Я уверен, что это может быть в дальнейшем.

Вот результаты тестовых случаев:

[1]
-> [0]

[1,1]
-> [0, 0]

[1,2]
-> x

[1,4,2]
-> [2, 0, 1]

[4,3,4]
-> [1, 0, 1]

[2,2,2]
-> [0, 0, 0]

[4,1,1,3]
-> [0, 2, 3, 0]

[3,2,4,4,4]
-> x

[2,3,4,3,2]
-> [0, 0, 3, 3, 1]

[4,2,1,2,3,2]
-> [2, 0, 2, 3, 3, 1]

[3,4,4,2,2,2,3,2,2,1,2,2,1,4,2,2,4,1,2,3,3,4]
-> [0, 3, 3, 0, 1, 3, 0, 0, 0, 0, 1, 3, 0, 2, 0, 2, 2, 2, 1, 1, 1, 1]

[2,2,2,3,1,2,4,4,3,3,4,4,3,2,1,3,1,3,2,2,4,4,2]
-> x

[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2]
-> [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0]

[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,3,4]
-> [1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 3, 3]

[4,1,2,2,2,4,1,3,1,4,4,4,1,1,4,4,4,1,4,3,2,4,3,4]
-> [1, 0, 3, 0, 0, 3, 2, 3, 1, 0, 0, 1, 0, 3, 1, 1, 3, 1, 0, 0, 2, 1, 2, 3]

Алгоритм сначала проверяет, чтобы значения всех блоков, кроме последнего, были одинаковыми (путем итерации и добавления к «счетчикам касаний» всех блоков, кроме первых 2). Затем, если количество блоков позволяет это ( (num_of_blocks - 1) % 3 != 1), возвращается и проверяет, соответствуют ли значения остальных блоков последнему блоку. Печатает, xесли нет решения.

kukac67
источник