Risolvi il puzzle di BattleBlock Theater


13

Il gioco BattleBlock Theater contiene occasionalmente un puzzle che è una versione generalizzata di Lights Out . Hai tre blocchi adiacenti, ognuno dei quali indica un livello compreso tra 1 e 4 inclusi con le barre, ad esempio:

|
||||
||

Se tocchi un blocco, quel blocco e qualsiasi blocco adiacente ne aumenteranno il livello (tornando indietro da 4 a 1). Il puzzle è risolto quando tutti e tre i blocchi mostrano lo stesso livello (non importa quale livello). Poiché l'ordine in cui tocchi i blocchi non ha importanza, indichiamo una soluzione con la frequenza con cui ogni blocco viene toccato. La soluzione ottimale per l'input sopra sarebbe 201:

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

Il gioco generalizza molto facilmente qualsiasi numero di blocchi, anche se per alcuni numeri, non tutte le configurazioni sono risolvibili.

La sfida

Data una sequenza di livelli di blocco, restituisci la frequenza con cui ogni blocco deve essere toccato per risolvere il puzzle. Ad esempio, l'esempio di cui sopra verrebbe fornito come 142e potrebbe dare 201come risultato. Se non esiste una soluzione, restituire un output coerente di propria scelta, che è distinguibile da tutte le potenziali soluzioni, ad esempio -1o una stringa vuota.

È possibile scrivere una funzione o un programma, accettare input tramite STDIN, argomento della riga di comando o argomento della funzione, in qualsiasi elenco o formato stringa conveniente, e allo stesso modo output tramite un valore di ritorno o stampando su STDOUT.

Il codice dovrebbe restituire i risultati corretti per tutti i casi di test entro un minuto su una macchina ragionevole. (Questo non è un limite completamente rigido, quindi se la tua soluzione richiede un minuto e dieci secondi, va bene, ma se impiega 3 minuti, non lo è. Un buon algoritmo li risolverà facilmente in pochi secondi.)

Questo è il golf del codice, quindi vince la risposta più breve (in byte).

Esempi

Le soluzioni non sono uniche, quindi potresti ottenere risultati diversi.

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

Per quanto ne so, ci sono esattamente 4 soluzioni per ogni ingresso in cui il numero di blocchi è 0 mod 3 o 1 mod 3 e ci sono 0 o 16 soluzioni in cui è 2 mod 3.


Hai bisogno di produrre una soluzione ottimale?
xnor

@xnor No, non lo fai.
Martin Ender,

Dobbiamo stampare esattamente una soluzione o possiamo anche stamparli tutti?
Jakube,

@Jakube Esattamente uno per favore. Avrei dovuto aggiungere un bonus per tutti / la soluzione ottimale, ma non ci ho pensato abbastanza presto, quindi qualsiasi (una) soluzione è.
Martin Ender,

Risposte:


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

Questa è la versione del programma che ho scritto mentre discutevo del problema con Martin.

L'ingresso è un elenco tramite STDIN. L'output è un elenco che rappresenta l'ultima soluzione trovata se esiste una soluzione o zero se non esiste. Per esempio:

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

Il porto obbligatorio. Grazie a @Jakube per il salvataggio di 3 byte.

Il metodo di input è lo stesso di sopra, provalo online .


Spiegazione (lunga e piena di logica!)

Innanzitutto, due osservazioni di base:

  • Osservazione 1: non importa quale ordine tocchi i blocchi

  • Osservazione 2: se tocchi un blocco 4 volte, equivale a toccarlo una volta

In altre parole, se esiste una soluzione, esiste una soluzione in cui il numero di tocchi è compreso tra 0 e 3 inclusi.

Poiché modulo 4 è così bello, facciamolo anche con i blocchi. Per il resto di questa spiegazione, il livello di blocco 0 equivale al livello di blocco 4.

Ora denotiamo a[k]il livello attuale di blocco ke x[k]il numero di volte in cui tocchiamo il blocco kin una soluzione. Sia anche nil numero totale di blocchi. Come ha notato @Jakube, una soluzione deve soddisfare:

  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

dove si Ctrova il livello finale su cui finiscono tutti i blocchi, tra 0 e 3 inclusi (ricorda che stiamo trattando il livello 4 come livello 0) e tutte le equazioni sopra sono in realtà congruenze modulo 4.

Ora ecco la parte divertente:

  • Osservazione 3 : se esiste una soluzione, esiste una soluzione per qualsiasi livello di blocco finale 0 <= C <= 3.

Esistono tre casi in base al numero di blocchi modulo 3. La spiegazione per ciascuno di essi è la stessa: per qualsiasi numero di blocchi esiste un sottoinsieme di blocchi che, se si tocca ciascuno di essi una volta, aumenta tutti i livelli di blocco di esattamente 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

Questo spiega perché ci sono 4 soluzioni per 0 mod 3e 1 mod 3, e di solito 16 soluzioni per 2 mod 3. Se hai già una soluzione, toccando i blocchi come sopra si ottiene un'altra soluzione che finisce a un livello di blocco più elevato (avvolgendo).

Che cosa significa questo? Possiamo scegliere qualsiasi livello di blocco finale Cche vogliamo! Scegliamo C = 0, perché questo risparmia sui byte.

Ora le nostre equazioni diventano:

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]

E riorganizzare:

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]

Quindi ciò che possiamo vedere è, se abbiamo x[0], quindi possiamo usare tutte le equazioni tranne l'ultima per scoprircix[k] . L'ultima equazione è una condizione aggiuntiva che dobbiamo verificare.

Questo ci dà un algoritmo:

  • Prova tutti i valori per x[0]
  • Usa le equazioni di cui sopra per elaborare tutte le altre x[k]
  • Controlla se l'ultima condizione è soddisfatta. In tal caso, salva la soluzione.

Questo dà la soluzione sopra.

Quindi perché a volte non otteniamo alcuna soluzione per 2 mod 3? Diamo di nuovo un'occhiata a questi due schemi:

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

Ora considera le equazioni in quelle posizioni, cioè per la prima:

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]

Aggiungili:

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

Per il secondo:

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]

Aggiungili di nuovo:

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

Quindi se (a[1] + a[4] + a[7] + a[10])e (a[0] + a[3] + a[6] + a[9])non sono uguali, allora non abbiamo soluzione. Ma se sono uguali, allora otteniamo 16 soluzioni. Questo è stato per il n = 11caso, ma ovviamente questo generalizza a qualsiasi numero lo sia2 mod 3 - prendi la somma di ogni terzo elemento a partire dal secondo e confronta con la somma di ogni terzo elemento a partire dal primo.

Ora finalmente, è possibile capire cosa x[0]deve essere invece di provare tutte le possibilità? Dopotutto, poiché abbiamo limitato il nostro livello target Ca 0, ce n'è solo uno x[0]che fornisce una soluzione nel caso 0 mod 3o 1 mod 3(as 4 solutions / 4 final levels = 1 solution for a specific final level).

La risposta è si! Possiamo farlo per 0 mod 3:

 .X..X
.X..X.

Che si traduce in:

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

Sottraendo si ottiene:

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

Allo stesso modo 1 mod 3, possiamo fare questo modello:

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

Che dà:

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

Questi ovviamente si generalizzano estendendo gli indici con incrementi di 3.

Poiché 2 mod 3, poiché abbiamo due sottoinsiemi che coprono ogni blocco, possiamo effettivamente sceglierne uno qualsiasi x[0]. In realtà, questo è vero per x[0], x[1], x[3], x[4], x[6], x[7], ...(praticamente qualsiasi indice non congruente a 2 mod 3, in quanto non sono coperti da nessuno dei due sottogruppi).

Quindi abbiamo un modo di scegliere un x[0]invece di provare tutte le possibilità ...

... ma la cattiva notizia è che non si risparmia sui 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

Intelligente. È possibile salvare 1 carattere utilizzando Jinvece di He 2 caratteri, se si apre l'ultimo elemento PJinvece di tagliare. <J_1. V4J]NVQaJ%_+s>J_2@QN4)I!eJPJB
Jakube,

@Jakube Ah grazie. Quando ho letto pop ho pensato che fosse come Python pop che restituiva l'ultimo elemento mentre si rimuoveva dall'elenco. Ora vedo che non è il caso.
Sp3000,

4

Pyth, 72 76 73 66 39 38 caratteri

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

modifica 4: realizzato, che i calcoli Q[N]-Q[N+1]+solution[-3]e Q[-2]-Q[-1]+solution[-3]sono ident. Pertanto, ho calcolato in eccesso la soluzione per 1 e ho filtrato le soluzioni, dove l'ultima voce è 0. Quindi apro l'ultima voce. Fortunatamente i casi speciali non richiedono un trattamento aggiuntivo con questo approccio. -27 caratteri

modifica 3: Applicazione di alcuni trucchi da golf da FryAmTheEggman: -7 caratteri

modifica 2: usando il filtro, riduci e mappa: -3 caratteri

modifica 1: Nella mia prima versione non ho stampato nulla, se non c'era soluzione. Non credo sia permesso, quindi +4 caratteri.

Si aspetta un elenco di numeri interi come input [1,4,2]e genera una soluzione valida [2,0,1]se esiste, altrimenti un elenco vuoto [].

Spiegazione:

Sia Ql'elenco di 5 livelli e Yl'elenco della soluzione. Le seguenti equazioni devono contenere:

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

Perciò se usiamo qualsiasi Y0e Y1, possiamo calcolare Y2, Y3e Y4nel modo seguente.

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

Di tutti i livelli tranne l'ultimo sono uguali (perché non abbiamo usato l'equazione = Q4 + Y3 + Y4. Per verificare, se quest'ultimo è uguale anche agli altri livelli, possiamo semplicemente verificare se (Q3 - Q4 + Y2) mod 4 == 0. Notare che la parte sinistra sarebbe il valore Y5Se calcolo la sesta parte della soluzione, posso semplicemente verificare se è zero.

Nel mio approccio ho semplicemente iterato su tutte le possibili partenze ( [0,0], a [3,3]) e ho calcolato la lunghezza (input) -1 ulteriori voci e filtrato tutte le soluzioni che terminano con uno zero.

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

è sostanzialmente il seguente:

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]

quindi filtro queste possibili soluzioni per quelle valide:

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

a questo elenco di soluzioni allego un elenco vuoto, in modo che contenga almeno un elemento

 +....]Y

e prendi la prima soluzione h, fai apparire l'ultimo elemento pe stampalo

 Ph

Si noti che funziona anche se esiste un solo blocco. Nel mio approccio ottengo la posizione iniziale [0,0] e non la prolungo. Poiché l'ultima voce è 0, stampa la soluzione [0].

Il secondo caso speciale (2 blocchi) non è poi così speciale. Non sono sicuro, perché ho complicato le cose in precedenza.


Penso che stampare nulla sia ok per nessuna soluzione se questo è l'unico caso in cui non stampi nulla. Potrebbe essere necessario confermare @ MartinBüttner
Sp3000 il

?**lQ]0qhQeQ<lQ3h+f!%-+ePQ@T_3eQ4mu+G]%+&H@G_3-@QH@QhH4UttQd^UT2]Yè 66 byte. Le prestazioni sono state un po 'colpite, ma per me è ancora il caso di test più grande in <1s. Ping me se vuoi spiegazioni su alcuni dei golf; non c'è abbastanza spazio in questo commento;) Spero ti
piaccia

+<list><int>ha lo stesso effetto di +<list>]<int>, quindi puoi rimuovere il primo ]. Inoltre, soluzione molto bella.
isaacg,

@isaacg È lo stesso per ~? Non sembrava essere quando ho provato
Sp3000 il

@ Sp3000 Basta sostituire ~con a- ~<list>]<int>equivale a a<list><int>. ~è +=, mentre aè.append()
isaacg il

3

Rubino, 320 313 caratteri

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

Si può sicuramente giocare a golf di più. Non produce nulla per i puzzle irrisolvibili.

Versione non golfata:

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

Ok, questo è stato divertente. Ecco l'algoritmo di base, con la {n}rappresentazione di n "touch" sul numero sopra il n, come dimostrato in uno degli esempi:

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

Sono rimasto un po 'perplesso qui. Come posso trasformare il 111...1110in una serie di stessi numeri? Quindi ho confrontato la mia soluzione e la soluzione corretta (nota: i conteggi "touch" sono tutti uno maggiore di quanto dovrebbero essere perché l'input è 1 indicizzato, mentre l'output è 0 indicizzato):

3033233103233301320210
0330130000130202221111

Ho notato che ogni numero era uno diverso da quello corretto mod 4, quindi li ho contrassegnati con +s, -s e =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)

Ha funzionato per un po ', fino a quando ho notato che a volte il risultato finale era 111...11112o 11...1113anche! Fortunatamente, applicare ripetutamente la formula magica che non ha senso ma funziona anche risolto questi.

Così il gioco è fatto. Un programma che inizia a dare un senso, ma si degrada in modo sempre più brutto. Abbastanza tipico per una soluzione di golf di codice, penso. :)


1
Adoro l'ultimo commento nel tuo codice :). Puoi salvare 2 caratteri cambiando exit if (r+=1)>5in (r+=1)>5&&exit. Inoltre, la (code)while condsintassi è più breve di 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

Sono sicuro che questo può essere ulteriormente giocato ..

Ecco i risultati dei casi di test:

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

L'algoritmo si assicura innanzitutto che i valori di tutti i blocchi tranne l'ultimo blocco siano gli stessi (iterando e aggiungendo ai "conteggi tattili" di tutti i blocchi tranne i primi 2). Quindi, se il numero di blocchi lo consente ( (num_of_blocks - 1) % 3 != 1), torna indietro e si assicura che i valori del resto dei blocchi corrispondano all'ultimo blocco. Stampa xse non c'è soluzione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.