Risoluzione della forza bruta di linea nonogramma


25

sfondo

Il nonogramma , noto anche come Picross o Griddlers, è un enigma in cui l'obiettivo è determinare se ogni cella sulla griglia 2D deve essere colorata o lasciata vuota, utilizzando il numero di celle colorate consecutive su ciascuna linea.

Di seguito è riportato un esempio di puzzle Nonogram con soluzione.

Il problema è che alcuni giochi / app mobili Nonogram commerciali hanno enigmi che non sono risolvibili manualmente (ad es. Hanno soluzioni multiple o richiedono un backtracking profondo). Tuttavia, offrono anche alcune vite al giocatore, dove si perde una vita quando si tenta di colorare una cella la cui risposta corretta è vuota . Quindi ora è il momento di forzare quei brutti "puzzle"!

Per semplificare l'attività, immagina solo una riga con il suo indizio e nient'altro:

3 7 | _ _ _ _ _  _ _ _ _ _  _ _ _ _ _

La [3,7]sono gli indizi, e la lunghezza della linea è 15 celle. Dal momento che ha molteplici possibili soluzioni, dobbiamo rischiare alcune vite per risolvere completamente questa linea (cioè determinare tutte le celle colorate).

Sfida

Data una linea con indizi (un elenco di numeri interi positivi) e la lunghezza della linea, trova il numero massimo di vite che perderai, supponendo che la forza bruta la linea con una strategia ottimale.

Nota che indovina sempre le celle colorate . Nei giochi reali, indovinare le celle vuote (giuste o sbagliate) non ha alcun effetto sulla tua vita, quindi non puoi "risolvere" il puzzle in quel modo.

Inoltre, puoi presumere che l'input rappresenti sempre una linea Nonogram valida, quindi non devi preoccuparti di qualcosa del genere [6], 5.

Spiegazione

Vediamo prima alcuni esempi più semplici.

[1,2], 5

Esistono esattamente tre possibilità per questa linea ( Oè una cella colorata, .è vuota):

O . O O .
O . . O O
. O . O O

Se proviamo a colorare la cella 0 (indice basato su 0 da sinistra), si verifica una delle seguenti condizioni:

  • La cella è colorata correttamente. Ora abbiamo due possibilità e possiamo scegliere tra la cella 2 e la cella 4 per risolvere completamente la linea. In entrambi i casi, nel peggiore dei casi perderemo una vita.
  • La cella è vuota e perdiamo una vita. In questo caso, abbiamo già identificato la soluzione unica per questa linea, quindi abbiamo finito con 1 punto vita perso.

Pertanto, la risposta per [1,2], 5è 1.

[5], 10

Ricerca binaria? No.

La prima scelta più ovvia è 4 o 5, che rivelerà una possibilità se è vuota (al costo di 1 vita). Diciamo che abbiamo scelto prima 4. Se la cella 4 è effettivamente colorata, la estendiamo a sinistra, cioè proviamo 3, 2, 1 e 0 fino a quando una vita non viene persa (o la cella 0 viene colorata, quindi finiamo per non spendere affatto vite). Ogni volta che si perde una vita, possiamo determinare in modo univoco la soluzione, ad esempio se vediamo qualcosa del genere:

_ _ X O O _ _ _ _ _

allora sappiamo già che la risposta è questa:

. . . O O O O O . .

Pertanto, la risposta [5], 10è anche 1.

[3,7], 15

Inizia con la cella 11, che, se vuota, rivelerà immediatamente la seguente soluzione.

O O O . O O O O O O O X . . .

Quindi prova 12, che, se vuoto, offre due possibilità che possono essere risolte al costo di 1 vita extra.

O O O . . O O O O O O O X . .
. O O O . O O O O O O O X . .

Ora prova 2. Se vuoto, porta a tre possibilità che possono essere risolte in modo simile [1,2], 5all'esempio.

. . X O O O . O O O O O O O .
. . X O O O . . O O O O O O O
. . X . O O O . O O O O O O O

Se continui a ridurre al minimo il rischio in questo modo, puoi raggiungere qualsiasi soluzione con max. 2 vite trascorse.

Casi test

[1,2] 5 => 1
[2] 5 => 2
[1] 5 => 4
[] 5 => 0
[5] 10 => 1
[2,1,5] 10 => 0
[2,4] 10 => 2
[6] 15 => 2
[5] 15 => 2
[4] 15 => 3
[3,7] 15 => 2
[3,4] 15 => 3
[2,2,4] 15 => 4
[1,1,1,1,1,1,1] 15 => 2

[2,1,1,3,1] 15 => 3
[1,1,1,2,1] 15 => 5

Negli ultimi due casi, la strategia ottimale non passa attraverso gli spazi minimi, ma va semplicemente da sinistra a destra (o da destra a sinistra). Grazie a @crashoz per averlo segnalato.

Regole

Si applicano le regole standard del . Vince l'invio valido più breve in byte.

generosità

Se qualcuno esce con un algoritmo del tempo polinomiale (con la prova della correttezza), assegnerò una taglia di +100 a tale soluzione.


A cosa serve l'output previsto [6], 5?
Leaky Nun,

Quando fai un'ipotesi, devi indovinare che la cella è nera o puoi indovinare in bianco o nero?
feersum

@LeakyNun È una linea non valida. Puoi supporre che l'input sia sempre una linea Nonogram valida.
Bubbler,

@feersum Indovina sempre le celle colorate. Nei giochi reali, indovinare una cella vuota (giusta o sbagliata) non ha alcun effetto sulla tua vita, quindi non puoi ottenere alcun feedback con essa.
Bubbler,

Sfida fantastica
Enrico Borba,

Risposte:


19

Rubino , 85 byte

f=->l,n,s=n-l.sum-l.size+1{*a,b=l;b&&s>0?(a[0]?1+f[a,n-b-2,s-1]:(n.to_f/b).ceil-1):0}

Provalo online!

Spiegazione

l=[l1,l2,...,lx]xn per il numero di celle.

lx
nlx
nlx1+f(l,nlx)
1+f(l~,nlx2)l~l

f(l,n)={0,if 1xli+x1nnlx1if x=11+max{f(l,nlx)f(l~,nlx2),otherwise

Ecco un esempio _sconosciuto, Xè uno spazio noto, Oè una cella colorata nota eL è persa la vita

[2,2,4] 15                  _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
(1) -> [2,2,4] 11           _ _ _ _ _ _ _ _ _ _ _ L X X X
    (1) -> [2,2,4] 7        _ _ _ _ _ _ _ L X X X L X X X
        0                   X X X L X X X L X X X L X X X
    (2) -> [2,2] 5          _ _ _ _ _ X O O O O L L X X X
        0                   O O X O O X O O O O L L X X X 
(2) -> [2,2] 9              _ _ _ _ _ _ _ _ _ X O O O O L
    (1) -> [2,2] 7          _ _ _ _ _ _ _ L X X O O O O L
        (1) -> [2,2] 5      _ _ _ _ _ L X L X X O O O O L
            0               O O X O O L X L X X O O O O L
        (2) -> [2] 3        _ _ _ X O O L L X X O O O O L
            1               O O L X O O L L X X O O O O L               
    (2) -> [2] 5            _ _ _ _ _ X O O L X O O O O L
        2                   O O L L X X O O L X O O O O L

Fa un albero binario, per perdere il numero di vite perse, dobbiamo solo contare l'altezza massima dell'albero. Tuttavia, ciò lo rende perché valutiamo tutti i rami. Possiamo fare di meglio.O(2n)

Definiamo una funzione di costo che ci aiuti a "scegliere" il ramo giusto ad ogni passaggio.h

h(l,n)=n1xlix+1

h conta il numero di spazi superflui che abbiamo se racchiudiamo tutti gli indizi con uno spazio in mezzo. Quindi è essenzialmente un indicatore di quante vite avremo bisogno per risolvere l'istanza, più è alta, più vite sono necessarie. L'idea è di applicare questo indicatore sulla nostra formula ricorsiva.

Per definizione di abbiamo, h

h(l,nlx)=nlx1xlix+1=(n1xlix+1)lx=h(l,n)lx

E,

h(l~,nlx2)=nlx21x1li(x1)+1=(n1xlix+1)1=h(l,n)1

Poi,

h(l,n)={0,if 1xli+x1nnlx,if x=1max{h(l,nlx)+lxh(l~,nlx2)+1,otherwise

Vogliamo massimizzare ad ogni passaggio per ottenere il caso peggiore, quindi controlliamo la differenza tra le due espressioni nella ricorrenzah

[h(l,nlx)+lx][h(l~,nlx2)+1]=nlxn1xlix+1+lx[nlx21x1li(x1)+1+1]=2

[h(l,nlx)+lx][h(l~,nlx2)+1]=2[h(l,nlx)+lx][h(l~,nlx2)+1]<0[h(l,nlx)+lx]<[h(l~,nlx2)+1]
Quindi la seconda espressione è sempre il massimo, possiamo rimuovere la prima

h(l,n)={0,if 1xli+x1nnlx,if x=1h(l~,nlx2)+1otherwise

Infine, questa definizione ricorsiva di ci mostra che l'opzione (2) nella funzione è sempre il caso peggiore (dando il massimo numero di possibilità, cioè massimizzando )hfh

f(l,n)={0,if 1xli+x1nnlx1if x=11+f(l~,nlx2),otherwise

Ad ogni passo diminuiamo di almeno 3 e ora c'è una singola chiamata ricorsiva, la complessità èO ( n )nO(n)


2
Benvenuto in PPCG, incredibile primo post!
Cole

1
@cole Non è il loro primo post, ma sicuramente è incredibile! Approccio molto intelligente +1
Mr. Xcoder

1
Lavoro fantastico. Premierò la taglia 2 giorni dopo, se nessuno troverà alcun grave difetto logico fino ad allora.
Bubbler,

2

Python, 303 289 byte

Primo golf da molto tempo, quindi potrebbe esserci molto grasso in eccesso. (Grazie a Jo King per aver trovato 14 byte di valore.)

La funzione f genera tutte le possibili disposizioni (anche se sempre con uno spazio come primo carattere, ma va bene purché aumentiamo la lunghezza di 1 prima di chiamarlo). La funzione g seleziona la posizione con il minor numero di spazi vuoti e di reclami. La funzione h li mette insieme.

f=lambda l,n:["."*i+"X"*l[0]+c for i in range(1,n-l[0]+1)for c in f(l[1:],n-i-l[0])]if l else["."*n]
def g(q,n):O,X=min([[[p[:i]+p[i+1:]for p in q if p[i]==u]for u in".X"]for i in range(n)],key=lambda x:len(x[0]));return(len(q)>1)*1and max(1+g(O,n-1),g(X,n-1))
h=lambda l,n:g(f(l,n+1),n+1)

Gli esempi funzionano tutti bene:

>>> h([3,7],15)
2
>>> h([3,4],15)
3
>>> h([1,1,1,2,1],15)
6


1
È consentito di tornare Falseper 0? In tal caso, puoi passare (len(q)>1)*1anda len(q)>1and. Se non ti è consentito tornare Falseper 0, allora fallo, ma cambia g(f(l,n+1),n+1)in 1*g(f(l,n+1),n+1)e salverà comunque un byte
Zacharý

1
Ancora meglio: nel caso in cui Falsenon sia consentito 0, invece di cambiarlo g(f(l,n+1),n+1)in 1*g(f(l,n+1),n+1), cambiarlo in+g(f(l,n+1),n+1)
Zacharý

2
Inoltre, non è necessario contare il conteggio dei h=byte
Zacharý

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.