Розв’яжіть головоломку театру 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 мод 3, або 1 мод 3, а є 0 або 16 рішень, де це 2 мод 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

Розумний. Ви можете зберегти 1 char, використовуючи Jзамість Hта 2 знаки, якщо вставити останній елемент PJзамість нарізки. <J_1. V4J]NVQaJ%_+s>J_2@QN4)I!eJPJB
Якубе

@Jakube Ах, дякую. Коли я читав поп, то думав, що це як поп Python, який повертає останній елемент, видаляючи зі списку. Тепер я бачу, що це не так.
Sp3000

4

Піта, 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], до [3,3]) і обчислюю довжину (введення) -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 блоки) зрештою не такий особливий. Не впевнений, чому я раніше ускладнював речі.


Я думаю, що друкувати нічого не в порядку, якщо немає рішення, якщо це не єдиний випадок, коли ви нічого не друкуєте. Можливо, вам доведеться попросити @ MartinBüttner, щоб підтвердити
Sp3000

?**lQ]0qhQeQ<lQ3h+f!%-+ePQ@T_3eQ4mu+G]%+&H@G_3-@QH@QhH4UttQd^UT2]Yстановить 66 байт. Виступ трохи вдарив, але він все ще веде найбільший тестовий випадок за <1s для мене. Пінг мені, якщо ви хочете пояснення деяких гольфів; в цьому коментарі не вистачає місця;) Сподіваюсь, вам подобається користуватися Pyth: D
FryAmTheEggman

+<list><int>має таку ж дію +<list>]<int>, що і ви можете видалити першу ]. Також дуже приємне рішення.
isaacg

@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якщо немає рішення.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.