Memecahkan Puzzle Teater BattleBlock

13

Permainan BattleBlock Theater kadang-kadang berisi teka-teki yang merupakan versi umum dari Lights Out . Anda memiliki tiga blok yang berdekatan, masing-masing menunjukkan tingkat antara 1 dan 4 termasuk dengan bar, misalnya:

|
||||
||

Jika Anda menyentuh sebuah blok, maka blok itu serta blok yang berdekatan akan menambah levelnya (membungkus kembali dari 4 ke 1). Teka-teki diselesaikan ketika ketiga blok menunjukkan level yang sama (tidak masalah level mana). Karena, urutan Anda menyentuh blok tidak menjadi masalah, kami menunjukkan solusi dengan seberapa sering setiap blok disentuh. Solusi optimal untuk input di atas adalah 201:

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

Gim ini sangat mudah menggeneralisasikan sejumlah blok, meskipun untuk beberapa angka, tidak semua konfigurasi dapat dipecahkan.

Tantangan

Diberikan urutan level blok, kembalikan seberapa sering setiap blok perlu disentuh untuk memecahkan teka-teki. Misalnya contoh di atas akan diberikan sebagai 142dan dapat menghasilkan 201sebagai hasilnya. Jika tidak ada solusi, kembalikan beberapa output yang konsisten dari pilihan Anda, yang dapat dibedakan dari semua solusi potensial, misalnya -1atau string kosong.

Anda dapat menulis suatu fungsi atau program, mengambil input melalui STDIN, argumen baris perintah atau argumen fungsi, dalam setiap format daftar atau string yang mudah, dan dengan cara yang sama menghasilkan melalui nilai balik atau dengan mencetak ke STDOUT.

Kode Anda harus mengembalikan hasil yang benar untuk semua kasus uji dalam satu menit pada mesin yang masuk akal. (Ini bukan batas yang sepenuhnya ketat, jadi jika solusi Anda membutuhkan satu menit dan sepuluh detik, itu baik-baik saja, tetapi jika dibutuhkan 3 menit, tidak. Algoritma yang baik akan dengan mudah menyelesaikannya dalam hitungan detik.)

Ini kode golf, jadi jawaban tersingkat (dalam byte) menang.

Contohnya

Solusi tidak unik, sehingga Anda dapat memperoleh hasil yang berbeda.

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

Sejauh yang saya tahu, ada tepat 4 solusi untuk setiap input di mana jumlah blok adalah 0 mod 3, atau 1 mod 3, dan ada 0 atau 16 solusi di mana itu adalah 2 mod 3.

Martin Ender
sumber
Apakah Anda perlu menampilkan solusi optimal?
xnor
@xnor Tidak, Anda tidak.
Martin Ender
Apakah kita harus mencetak tepat satu solusi atau dapatkah kita juga mencetak semuanya?
Jakube
@ Jakube Tepat satu tolong. Saya seharusnya menambahkan bonus untuk semua / solusi optimal, tapi saya tidak memikirkannya cukup awal, jadi ada satu solusi.
Martin Ender

Jawaban:

10

Python 2, 115 byte

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

Ini adalah versi golf dari program yang saya tulis ketika membahas masalah dengan Martin.

Input adalah daftar melalui STDIN. Output adalah daftar yang mewakili solusi terakhir yang ditemukan jika ada solusi, atau nol jika tidak ada. Sebagai contoh:

>>>
[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 byte

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

Pelabuhan wajib. Berkat @ Jakube untuk penghematan 3 byte.

Metode input sama seperti di atas, coba online .


Penjelasan (panjang dan penuh logika!)

Pertama, dua pengamatan dasar:

  • Pengamatan 1: Tidak masalah urutan mana yang Anda sentuh blok

  • Pengamatan 2: Jika Anda menyentuh blok 4 kali, itu setara dengan menyentuhnya satu kali

Dengan kata lain, jika ada solusi maka ada solusi di mana jumlah sentuhan adalah antara 0 dan 3 inklusif.

Karena modulo 4 sangat bagus, mari kita lakukan dengan balok juga. Untuk sisa penjelasan ini, level blok 0 setara dengan level blok 4.

Sekarang mari kita menyatakan a[k]sebagai level blok saat inik dan x[k]menjadi berapa kali kita menyentuh blok kdalam suatu solusi. Juga biarlah njumlah total blok. Seperti yang telah dicatat @Jakube, solusi harus memuaskan:

  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

di mana Clevel akhir semua blok berakhir, antara 0 dan 3 inklusif (ingat kita memperlakukan level 4 sebagai level 0) dan semua persamaan di atas benar-benar sesuai dengan modulo 4.

Sekarang inilah bagian yang menyenangkan:

  • Pengamatan 3 : Jika solusi ada, solusi ada untuk setiap level blok akhir 0 <= C <= 3.

Ada tiga kasus berdasarkan jumlah blok modulo 3. Penjelasan untuk masing-masing blok adalah sama - untuk sejumlah blok, ada subset blok yang, jika Anda menyentuh masing-masing satu kali, menambah semua level blok dengan tepat 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

Ini menjelaskan mengapa ada 4 solusi untuk 0 mod 3 dan 1 mod 3, dan biasanya 16 solusi untuk 2 mod 3. Jika Anda sudah memiliki solusi, menyentuh blok seperti di atas memberikan solusi lain yang berakhir pada level blok yang lebih tinggi (melilit).

Jadi apa artinya ini? Kami dapat memilih level blok akhir mana punC kami inginkan! Mari kita pilih C = 0, karena ini menghemat byte.

Sekarang persamaan kami menjadi:

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]

Dan mengatur ulang:

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]

Jadi yang bisa kita lihat adalah, jika ada x[0] , maka kita bisa menggunakan semua persamaan kecuali yang terakhir untuk mencari tahu satu sama lain x[k]. Persamaan terakhir adalah kondisi tambahan yang harus kita periksa.

Ini memberi kami sebuah algoritma:

  • Coba semua nilai untuk x[0]
  • Gunakan persamaan di atas untuk menghitung semua yang lain x[k]
  • Periksa apakah kondisi terakhir terpenuhi. Jika demikian, simpan solusinya.

Itu memberikan solusi di atas.

Jadi mengapa kita terkadang tidak mendapat solusi 2 mod 3? Mari kita lihat dua pola ini lagi:

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

Sekarang perhatikan persamaan pada posisi-posisi itu, yaitu untuk yang pertama:

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]

Tambahkan mereka:

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

Untuk yang kedua:

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]

Tambahkan lagi:

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

Jadi jika (a[1] + a[4] + a[7] + a[10])dan (a[0] + a[3] + a[6] + a[9])tidak sama, maka kita tidak punya solusi. Tetapi jika mereka sama, maka kita mendapatkan 16 solusi. Ini untukn = 11 kasus ini, tetapi tentu saja hal ini bersifat umum ke nomor apa pun 2 mod 3- ambil jumlah setiap elemen ketiga mulai dari yang kedua, dan bandingkan dengan jumlah setiap elemen ketiga mulai dari yang pertama.

Sekarang akhirnya, apakah mungkin untuk mencari tahu apa yang x[0]harus terjadi daripada mencoba semua kemungkinan? Setelah semua, karena kita dibatasi tingkat target kami Cuntuk menjadi 0, hanya ada satu x[0]yang memberikan solusi dalam 0 mod 3atau 1 mod 3kasus (sebagai4 solutions / 4 final levels = 1 solution for a specific final level ).

Jawabannya iya! Kita dapat melakukan ini untuk 0 mod 3:

 .X..X
.X..X.

Yang diterjemahkan menjadi:

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]   /

Mengurangkan memberi:

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

Demikian pula untuk 1 mod 3kita dapat melakukan pola ini:

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

Pemberian yang mana:

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

Ini tentu saja menggeneralisasi dengan memperluas indeks dengan kenaikan 3.

Karena 2 mod 3, karena kami memiliki dua himpunan bagian yang mencakup setiap blok, kami dapat memilihnya x[0]. Bahkan, ini berlaku untuk x[0], x[1], x[3], x[4], x[6], x[7], ...(pada dasarnya setiap indeks tidak kongruen 2 mod 3, karena mereka tidak tercakup oleh salah satu bagian).

Jadi kami memiliki cara memilih x[0]alih - alih mencoba semua kemungkinan ...

... tetapi berita buruknya adalah ini tidak menghemat byte (124 byte):

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
sumber
Pintar. Anda dapat menyimpan 1 char dengan menggunakan Jalih-alih Hdan 2 karakter, jika Anda menghapus elemen terakhir PJalih-alih mengiris. <J_1. V4J]NVQaJ%_+s>J_2@QN4)I!eJPJB
Jakube
@ Jakube Ah terima kasih. Ketika saya membaca pop saya pikir itu seperti Python pop mengembalikan elemen terakhir sambil menghapus dari daftar. Sekarang saya tahu bukan itu masalahnya.
Sp3000
4

Pyth, 72 76 73 66 39 38 karakter

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

sunting 4: Direalisasikan, bahwa perhitungan Q[N]-Q[N+1]+solution[-3]dan Q[-2]-Q[-1]+solution[-3]diidentifikasi. Karena itu saya menghitung terlalu tinggi solusinya dengan 1, dan menyaring solusi, di mana entri terakhir adalah 0. Lalu saya memunculkan entri terakhir. Untungnya kasus-kasus khusus tidak memerlukan perawatan tambahan dengan pendekatan ini. -27 karakter

sunting 3: Menerapkan beberapa trik golf dari FryAmTheEggman: -7 karakter

sunting 2: Menggunakan filter, kurangi dan petakan: -3 karakter

sunting 1: Dalam versi pertama saya, saya tidak mencetak apa pun, jika tidak ada solusi. Saya tidak berpikir itu diperbolehkan, oleh karena itu karakter +4.

Mengharapkan daftar bilangan bulat sebagai input [1,4,2]dan mengeluarkan solusi yang valid [2,0,1]jika ada, jika tidak, daftar kosong [].

Penjelasan:

Membiarkan Qmenjadi daftar 5 level dan Ydaftar solusi. Persamaan berikut harus dipegang:

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

Karena itu jika kita menggunakan Y0dan Y1, kita dapat menghitung Y2, Y3dan Y4dengan cara berikut.

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

Dari semua level kecuali yang terakhir sama (karena kami tidak menggunakan persamaan = Q4 + Y3 + Y4. Untuk mengecek, jika yang terakhir ini juga sama dengan level yang lain, kita cukup memeriksa apakah (Q3 - Q4 + Y2) mod 4 == 0. Perhatikan, bahwa bagian kiri akan menjadi nilai Y5Jika saya menghitung bagian keenam dari solusi, saya dapat dengan mudah memeriksa, apakah itu nol.

Dalam pendekatan saya, saya hanya mengulangi semua mulai mungkin ( [0,0], untuk [3,3]), dan menghitung panjang (input) -1 entri lebih banyak dan menyaring semua solusi yang berakhir dengan nol.

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

pada dasarnya adalah sebagai berikut:

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]

lalu saya filter solusi yang mungkin untuk yang valid:

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

untuk daftar solusi ini saya tambahkan daftar kosong, sehingga memiliki setidaknya satu item di dalamnya

 +....]Y

dan ambil solusi pertama h, pop elemen terakhir pdan cetak

 Ph

Perhatikan, bahwa ini juga berfungsi, jika hanya ada satu blok. Dalam pendekatan saya, saya mendapatkan posisi awal [0,0] dan tidak memperpanjangnya. Karena entri terakhir adalah 0, ia mencetak solusinya [0].

Kasus spesial kedua (2 blok) sama sekali tidak spesial. Tidak yakin, mengapa saya terlalu rumit sebelumnya.

Jakube
sumber
Saya pikir mencetak tidak ada apa-apa tanpa solusi jika itu satu-satunya kasus di mana Anda mencetak apa-apa. Mungkin harus meminta @ MartinBüttner untuk mengonfirmasi
Sp3000
?**lQ]0qhQeQ<lQ3h+f!%-+ePQ@T_3eQ4mu+G]%+&H@G_3-@QH@QhH4UttQd^UT2]Yadalah 66 byte. Performanya sedikit terpukul, tetapi masih menjalankan test case terbesar dalam <1s untuk saya. Ping saya jika Anda ingin penjelasan tentang beberapa golf; tidak ada ruang yang cukup dalam komentar ini;) Harap Anda menikmati menggunakan Pyth: D
FryAmTheEggman
+<list><int>memiliki efek yang sama dengan +<list>]<int>, jadi Anda dapat menghapus yang pertama ]. Juga, solusi yang sangat bagus.
isaacg
@isaacg Apakah hal yang sama berlaku untuk ~? Sepertinya tidak ketika saya mencoba
Sp3000
@ Sp3000 Hanya ganti ~dengan a- ~<list>]<int>sama dengan a<list><int>. ~adalah +=, sementara aini.append()
isaacg
3

Ruby, 320 313 karakter

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*''

Pasti bisa main golf lagi. Tidak menghasilkan apa-apa untuk puzzle yang tidak dapat diselesaikan.

Versi tidak disatukan:

#!/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 * ''

Oke, ini menyenangkan. Inilah algoritma dasar, dengan {n}mewakili n "sentuhan" pada angka di atas n, seperti yang ditunjukkan pada salah satu contoh:

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

Saya sedikit bingung di sini. Bagaimana saya bisa mengubahnya 111...1110menjadi serangkaian angka yang sama? Jadi saya membandingkan solusi saya dan solusi yang benar (catatan: jumlah "sentuhan" semuanya satu lebih besar dari yang seharusnya karena input 1-diindeks, sedangkan outputnya adalah 0-diindeks):

3033233103233301320210
0330130000130202221111

Saya perhatikan bahwa setiap angka adalah satu dari yang benar mod 4, jadi saya menandainya dengan +s, -s, dan= 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)

Itu bekerja sebentar, sampai saya perhatikan bahwa kadang-kadang hasil akhirnya 111...11112 atau11...1113 juga! Untungnya, berulang kali menerapkan formula ajaib yang tidak masuk akal tetapi bekerja juga memilah ini.

Jadi, begitulah. Sebuah program yang mulai masuk akal, tetapi menurun menjadi lebih banyak dan lebih buruk lagi. Cukup khas untuk solusi kode golf, saya pikir. :)

Gagang pintu
sumber
1
Saya suka komentar terakhir dalam kode Anda :). Anda dapat menyimpan 2 karakter dengan mengubah exit if (r+=1)>5ke (r+=1)>5&&exit. Juga, (code)while condsintaks lebih pendek dari while cond \n code \n end.
Cristian Lupascu
2

Python 2, 294.289.285.281 273 byte

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

Saya yakin ini bisa bermain golf lebih lanjut ..

Berikut adalah hasil dari kasus uji:

[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]

Algoritme pertama-tama memastikan bahwa nilai semua blok kecuali blok terakhir adalah sama (dengan mengulangi dan menambahkan "jumlah sentuhan" semua blok kecuali 2 yang pertama). Kemudian, jika jumlah blok memungkinkan ( (num_of_blocks - 1) % 3 != 1), kembali dan pastikan nilai sisa blok sesuai dengan blok terakhir. Mencetak xjika tidak ada solusi.

kukac67
sumber