Algoritmo di lancio di bombe


212

Ho una n x mmatrice composta da numeri interi non negativi. Per esempio:

2 3 4 7 1
1 5 2 6 2
4 3 4 2 1
2 1 2 4 1
3 1 3 4 1
2 1 4 3 2
6 9 1 6 4

"Lanciare una bomba" diminuisce di uno il numero della cella bersaglio e di tutti gli otto dei suoi vicini, fino a un minimo di zero.

x x x 
x X x
x x x

Qual è un algoritmo che determinerebbe il numero minimo di bombe necessarie per ridurre a zero tutte le celle?

Opzione B (a causa del fatto che non sono un lettore attento)

In realtà la prima versione del problema non è quella per cui sto cercando una risposta. Non ho letto attentamente l'intero compito, ci sono vincoli aggiuntivi, diciamo:

Che dire del problema semplice, quando la sequenza in fila deve essere non crescente:

8 7 6 6 5 è possibile la sequenza di input

7 8 5 5 2 non è possibile poiché 7 -> 8 crescono in sequenza.

Forse trovare una risposta per un caso "più facile" aiuterebbe a trovare una soluzione per un caso più difficile.

PS: credo che quando abbiamo diverse stesse situazioni che richiedono un minimo di bombe per liberare la linea superiore, scegliamo quella che usa la maggior parte delle bombe sul "lato sinistro" della fila. Ancora qualche prova che potrebbe essere corretta?


4
Bene, trovo solo che alcuni campi possono essere saltati come nell'esempio 2 3 1 5 Rilasciarlo su 2,3,1 è inutile, perché lasciarlo cadere su di essi causa alcuni danni subset che possiamo causare rilasciando su 5. Ma non è possibile scopri come farlo funzionare a livello globale (se è corretto). Eliminare 2 richiede l'uso di 2 bombe sganciate su uno qualsiasi dei vicini e 5 contiene altri set di danni. Ma poi non so cosa fare più tardi da quando lo riscrivi (dopo averlo diminuito), allora hai due scelte (non c'è un superinsieme di danni).
abc

24
Questo NP-difficile è per caso? Sembra essere una variante del problema della copertura massima .
Mysticial

14
+1 per avermi dato qualcosa di interessante a cui pensare
Nick Mitchinson

3
@ Kostek, bel problema! Inserisci il link.
Colonel Panic

5
forse dovresti chiarire, hai detto che la domanda è: what's the minimum amount of bombs required to clean the board?questo significa che non è necessariamente necessario per trovare un modello di bombardamento effettivo, ma solo il numero minimo di bombe?
Lie Ryan

Risposte:


38

C'è un modo per ridurre questo problema a un semplice problema secondario.

Ci sono 2 parti per la spiegazione, l'algoritmo e il motivo per cui l'algoritmo fornisce una soluzione ottimale. Il primo non avrebbe senso senza il secondo, quindi inizierò con il perché.

Se pensi di bombardare il rettangolo (supponi un rettangolo grande - ancora nessun caso limite) puoi vedere che l'unico modo per ridurre a 0 il rettangolo vuoto dei quadrati sul perimetro è bombardare il perimetro o bombardare il rettangolo cavo di quadrati appena all'interno del perimetro. Chiamerò il perimetro strato 1 e il rettangolo al suo interno strato 2.

Un'intuizione importante è che non esiste un punto bombardamento strato 1, perché il "raggio dell'esplosione" che ottieni è sempre contenuto nel raggio dell'esplosione di un altro quadrato dello strato 2. Dovresti essere in grado di convincerti facilmente di questo.

Quindi, possiamo ridurre il problema a trovare un modo ottimale per bombardare il perimetro, quindi possiamo ripeterlo fino a quando tutti i quadrati sono 0.

Ma ovviamente, ciò non troverà sempre una soluzione ottimale se è possibile bombardare il perimetro in modo non ottimale, ma usando X bombe extra il problema di ridurre lo strato interno è più semplice di> X bombe. Quindi, se chiamiamo permiter layer uno, se posizioniamo X bombe extra da qualche parte nel layer 2 (appena dentro lo strato 1), possiamo ridurre lo sforzo di bombardare lo strato 2 in seguito di più di X? In altre parole, dobbiamo dimostrare che possiamo essere avidi nel ridurre il perimetro esterno.

Ma sappiamo che possiamo essere avidi. Perché nessuna bomba nello strato 2 potrà mai essere più efficiente nel ridurre lo strato 2 a 0 di una bomba posizionata strategicamente nello strato 3. E per lo stesso motivo di prima - c'è sempre una bomba che possiamo posizionare nello strato 3 che influenzerà ogni quadrato dello strato 2 che una bomba posta nello strato 2 può. Quindi, non può mai farci del male essere avidi (in questo senso di avido).

Quindi, tutto ciò che dobbiamo fare è trovare il modo ottimale per ridurre il permiter a 0 bombardando il successivo strato interno.

Non siamo mai feriti bombardando prima l'angolo a 0, perché solo l'angolo dello strato interno può raggiungerlo, quindi non abbiamo davvero scelta (e, qualsiasi bomba sul perimetro che può raggiungere l'angolo ha un raggio di esplosione contenuto nel raggio di esplosione dall'angolo dello strato interno).

Fatto ciò, i quadrati sul perimetro adiacente all'angolo 0 possono essere raggiunti solo da 2 quadrati dallo strato interno:

0       A       B

C       X       Y

D       Z

A questo punto il perimetro è effettivamente un anello unidimensionale chiuso, perché qualsiasi bomba ridurrà 3 quadrati adiacenti. Tranne qualche stranezza vicino agli angoli: X può "colpire" A, B, C e D.

Ora non possiamo usare alcun trucco del raggio di esplosione: la situazione di ogni quadrato è simmetrica, ad eccezione degli angoli strani, e anche lì nessun raggio di esplosione è un sottoinsieme di un altro. Nota che se questa fosse una linea (come discute il colonnello Panic) invece di un circuito chiuso, la soluzione è banale. I punti finali devono essere ridotti a 0 e non ti fa mai male bombardare i punti adiacenti ai punti finali, ancora una volta perché il raggio dell'esplosione è un superset. Una volta che hai impostato il tuo endpoint 0, hai ancora un nuovo endpoint, quindi ripeti (finché la linea è tutta 0).

Quindi, se possiamo ridurre in modo ottimale un singolo quadrato nel livello a 0, abbiamo un algoritmo (perché abbiamo tagliato il loop e ora abbiamo una linea retta con i punti finali). Credo che il bombardamento adiacente al quadrato con il valore più basso (dandoti 2 opzioni) in modo tale che il valore più alto entro 2 quadrati di quel valore più basso sia il minimo possibile (potresti dover dividere il tuo bombardamento per gestirlo) sarà ottimale ma io non hai (ancora?) una prova.


+1 - Stavo per scrivere qualcosa di simile. Penso che tu abbia capito!
Rex Kerr

5
@beaker, leggi attentamente il problema. Bombardare un quadrato riduce tutti e otto i suoi vicini, quindi la sua supposizione è effettivamente corretta.
darksky

20
But, we do know we can be greedy...- Non lo sto comprando. Considera 1 1 2 1 1 2il perimetro. Il numero minimo di bombe è 4, ma ci sono tre soluzioni distinte. Ogni soluzione ha un impatto diverso sul livello successivo. Finché esistono più soluzioni minime per un perimetro, non è possibile isolare completamente il perimetro senza considerare gli strati interni. Non credo davvero che questo problema possa essere risolto senza tornare indietro.
user1354557

4
Stavo pensando a questa soluzione, ma sembra così semplice. È vero che puoi sganciare una bomba allo strato 2 per pulire lo strato 1, ma se ci sono più soluzioni, esse effettuano soluzioni per strati superiori.
Luka Rahne

12
@psr: non funziona. Il metodo di bombardamento ottimale per lo strato esterno potrebbe non essere ottimale a livello globale. Esempio: 0011100 0100010 0000000 0000000 1110111. Il modo ottimale per bombardare il primo strato è quello di bombardare al centro della seconda fila, prendendo un totale di tre bombe per uccidere lo strato esterno. Ma poi hai bisogno di due bombe per occuparti del livello successivo. Ottimale richiede solo quattro bombe in totale: due per le prime due file e due per l'ultima fila.
nneonneo

26

Pólya dice "Se non puoi risolvere un problema, allora c'è un problema più semplice che puoi risolvere: trovalo".

L'ovvio problema più semplice è il problema unidimensionale (quando la griglia è una singola riga). Cominciamo con l'algoritmo più semplice: bombardare avidamente l'obiettivo più grande. Quando va storto?

Dato 1 1 1, l'algoritmo avido è indifferente a quale cella bombarda per prima. Ovviamente, la cella centrale è migliore: azzera tutte e tre le celle contemporaneamente. Ciò suggerisce un nuovo algoritmo A, "bomba per ridurre al minimo la somma rimanente". Quando questo algoritmo va storto?

Dato 1 1 2 1 1, l'algoritmo A è indifferente tra il bombardamento della 2a, 3a o 4a cella. Ma bombardare la seconda cella per andarsene 0 0 1 1 1è meglio che bombardare la terza cella per andarsene 1 0 1 0 1. Come risolverlo? Il problema con il bombardamento della 3a cella è che ci lascia il lavoro a sinistra e il lavoro a destra che deve essere fatto separatamente.

Che ne dici di "bombardare per ridurre al minimo la somma rimanente, ma massimizzare il minimo a sinistra (di dove abbiamo bombardato) più il minimo a destra". Chiama questo algoritmo B. Quando questo algoritmo va storto?


Modifica: dopo aver letto i commenti, sono d'accordo che un problema molto più interessante sarebbe il problema unidimensionale modificato in modo che le estremità si uniscano. Mi piacerebbe vedere dei progressi su questo.


40
Non sono sicuro del motivo per cui questa risposta sta ottenendo così tanti voti positivi: il caso 1D è quasi banale, semplicemente bomba sempre l'elemento a destra del primo elemento positivo. Questo funziona perché c'è sempre esattamente un modo ottimale per bombardare qualsiasi elemento che contiene solo 0 alla sua sinistra. Questo può essere esteso a 2D per rimuovere in modo ottimale i quadrati d'angolo, ma non vedo un modo ovvio per estenderlo oltre ...?
BlueRaja - Danny Pflughoeft

3
@BlueRaja, ho votato contro perché ha chiaramente illustrato che l'approccio avido discusso nelle altre risposte era insufficiente (almeno, doveva essere integrato con un criterio aggiuntivo). Alcune scelte di target, anche se si traducono in una pari riduzione del numero totale, possono lasciare le cose più disperse di altre. Penso che questa sia una visione utile per il problema 2D.
Tim Goodman

3
E in generale "Se sei bloccato sul case 2D, prova prima il case 1D" è un buon consiglio.
Tim Goodman

22
@Tim: "'prova prima la custodia 1D' è un buon consiglio" Sì, il che lo renderebbe un commento eccellente; ma non è una risposta ...
BlueRaja - Danny Pflughoeft

3
Penso che tu abbia un buon punto sul fatto che il caso 1D potrebbe essere un po 'fuorviante qui perché ha una soluzione semplice che non si estende prontamente a dimensioni superiori. Penso che il caso 1D con condizioni al contorno periodiche (il caso wrap around) potrebbe essere migliore.
Tim Goodman

12

Mi sono dovuto fermare solo a una soluzione parziale poiché ero fuori tempo, ma si spera che anche questa soluzione parziale fornisca alcuni spunti su un potenziale approccio per risolvere questo problema.

Di fronte a un problema difficile, mi piace trovare problemi più semplici per sviluppare un'intuizione sullo spazio del problema. Qui, il primo passo che ho fatto è stato ridurre questo problema 2D in un problema 1-D. Considera una linea:

0 4 2 1 3 0 1

In un modo o nell'altro, sai che dovrai bombardare il 4punto o intorno al punto 4 volte per farlo scendere a 0. Poiché la sinistra del punto è un numero inferiore, non c'è alcun vantaggio nel bombardare il punto0 o il 4bombardare eccessivamente il 2. In effetti, credo (ma non ho una prova rigorosa) che bombardare il 2fino a quando lo 4spot scende a 0 sia almeno buono quanto qualsiasi altra strategia per farlo 4scendere a 0. Si può procedere lungo la linea da sinistra a destra in una strategia come questo:

index = 1
while index < line_length
  while number_at_index(index - 1) > 0
    bomb(index)
  end
  index++
end
# take care of the end of the line
while number_at_index(index - 1) > 0
  bomb(index - 1)
end

Un paio di ordini di bombardamento di esempio:

0 4[2]1 3 0 1
0 3[1]0 3 0 1
0 2[0]0 3 0 1
0 1[0]0 3 0 1
0 0 0 0 3[0]1
0 0 0 0 2[0]0
0 0 0 0 1[0]0
0 0 0 0 0 0 0

4[2]1 3 2 1 5
3[1]0 3 2 1 5
2[0]0 3 2 1 5
1[0]0 3 2 1 5
0 0 0 3[2]1 5
0 0 0 2[1]0 5
0 0 0 1[0]0 5
0 0 0 0 0 0[5]
0 0 0 0 0 0[4]
0 0 0 0 0 0[3]
0 0 0 0 0 0[2]
0 0 0 0 0 0[1]
0 0 0 0 0 0 0

L'idea di iniziare con un numero che deve scendere in un modo o nell'altro è allettante perché diventa improvvisamente realizzabile trovare una soluzione che come alcuni affermano di essere valida almeno quanto tutte le altre soluzioni.

Il prossimo passo in avanti nella complessità in cui questa ricerca almeno altrettanto buona è ancora fattibile è sul bordo del tabellone. È chiaro per me che non c'è mai alcun vantaggio assoluto nel bombardare il bordo esterno; faresti meglio a bombardare il punto e ottenere altri tre spazi gratuitamente. Detto questo, possiamo dire che bombardare l'anello uno all'interno del bordo è almeno altrettanto buono che bombardare il bordo. Inoltre, possiamo combinare questo con l'intuizione che bombardare quello giusto all'interno del bordo è in realtà l'unico modo per portare gli spazi del bordo fino a 0. Ancora di più, è banalmente semplice capire la strategia ottimale (in quanto è a buono almeno quanto qualsiasi altra strategia) per ridurre i numeri degli angoli a 0. Mettiamo tutto insieme e possiamo avvicinarci molto a una soluzione nello spazio 2-D.

Data l'osservazione sui pezzi d'angolo, possiamo dire con certezza di conoscere la strategia ottimale per passare da qualsiasi tabellone di partenza a un tabellone con zeri su tutti gli angoli. Questo è un esempio di una tavola del genere (ho preso in prestito i numeri dalle due tavole lineari sopra). Ho etichettato alcuni spazi in modo diverso e spiegherò perché.

0 4 2 1 3 0 1 0
4 x x x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

Si noterà nella fila superiore in realtà assomiglia da vicino l'esempio lineare abbiamo visto in precedenza. Ricordando la nostra precedente osservazione che il modo ottimale per portare tutta la riga in alto a 0 è bombardare la seconda riga (la xriga). Non c'è modo di cancellare la riga superiore bombardando una qualsiasi delle yrighe e nessun vantaggio aggiuntivo nel bombardare la riga superiore rispetto al bombardamento dello spazio corrispondente sulla xriga.

Abbiamo potuto applicare la strategia lineare dall'alto (bombardamento spazi corrispondenti alla xfila), preoccupandoci solo con la fila superiore e nient'altro. Sarebbe qualcosa del genere:

0 4 2 1 3 0 1 0
4 x[x]x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

0 3 1 0 3 0 1 0
4 x[x]x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

0 2 0 0 3 0 1 0
4 x[x]x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

0 1 0 0 3 0 1 0
4 x[x]x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

0 0 0 0 3 0 1 0
4 x x x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

Il difetto di questo approccio diventa molto evidente negli ultimi due attentati. È chiaro, dato che gli unici siti di bombe che riducono la 4cifra nella prima colonna nella seconda riga sono il primo xe ily . Gli ultimi due bombardamenti sono chiaramente inferiori al solo bombardamento del primo x, che avrebbe fatto esattamente lo stesso (per quanto riguarda il primo punto della fila in alto, che non abbiamo altro modo di eliminare). Poiché abbiamo dimostrato che la nostra strategia attuale non è ottimale, è chiaramente necessaria una modifica della strategia.

A questo punto, posso fare un passo indietro nella complessità e concentrarmi su un solo angolo. Consideriamo questo:

0 4 2 1
4 x y a
2 z . .
1 b . .

E 'chiaro l'unico modo per ottenere gli spazi con 4fino a zero sono di bombardare una combinazione di x, ye z. Con alcune acrobazie in mente, sono abbastanza sicuro che la soluzione ottimale sia bombardare xtre volte e apoi b. Ora si tratta di capire come ho raggiunto quella soluzione e se rivela qualche intuizione possiamo usare anche per risolvere questo problema locale. Noto che non ci sono bombardamenti ye zspazi. Il tentativo di trovare un angolo in cui bombardare quegli spazi abbia senso produce un angolo simile a questo:

0 4 2 5 0
4 x y a .
2 z . . .
5 b . . .
0 . . . .

Per questo, mi è chiaro che la soluzione ottimale è bombardare y5 volte e z5 volte. Facciamo un ulteriore passo avanti.

0 4 2 5 6 0 0
4 x y a . . .
2 z . . . . .
5 b . . . . .
6 . . . . . .
0 . . . . . .
0 . . . . . .

Qui, sembra altrettanto intuitivo che la soluzione ottimale sia bombardare ae b6 volte e poi x4 volte.

Ora diventa un gioco su come trasformare quelle intuizioni in principi su cui possiamo costruire.

Si spera di continuare!


10

Per la domanda aggiornata un semplice algoritmo avido fornisce un risultato ottimale.

Sgancia le bombe A [0,0] nella cella A [1,1], quindi rilascia le bombe A [1,0] nella cella A [2,1] e continua questo processo verso il basso. Per pulire l'angolo inferiore sinistro, rilascia le bombe max (A [N-1,0], A [N-2,0], A [N-3,0]) nella cella A [N-2,1]. Questo pulirà completamente le prime 3 colonne.

Con lo stesso approccio pulire le colonne 3,4,5, quindi le colonne 6,7,8, ecc.

Purtroppo questo non aiuta a trovare una soluzione per il problema originale.


Si può dimostrare che il problema "più grande" (senza vincoli "non crescenti") è NP-difficile. Ecco uno schizzo di una prova.

Supponiamo di avere un grafico planare di grado fino a 3. Troviamo la copertura minima dei vertici per questo grafico. Secondo l'articolo di Wikipedia questo problema è NP-difficile per i grafici planari di grado fino a 3. Ciò potrebbe essere dimostrato dalla riduzione da Planar 3SAT. E durezza di Planar 3SAT - mediante riduzione da 3SAT. Entrambe queste prove sono presentate in recenti lezioni in "Limiti inferiori algoritmici" del prof. Erik Demaine (lezioni 7 e 9).

Se dividiamo alcuni bordi del grafico originale (grafico a sinistra nel diagramma), ciascuno con un numero pari di nodi aggiuntivi, il grafico risultante (grafico a destra nel diagramma) dovrebbe avere esattamente la stessa copertura del vertice minimo per i vertici originali. Tale trasformazione consente di allineare i vertici del grafo a posizioni arbitrarie sulla griglia.

inserisci qui la descrizione dell'immagine

Se posizioniamo i vertici del grafo solo su righe e colonne pari (in modo tale che non ci siano due spigoli incidenti su un vertice formino un angolo acuto), inseriamo "uno" ovunque sia presente un bordo e inseriamo "zeri" in altre posizioni della griglia, potremmo usare qualsiasi soluzione per il problema originale per trovare la copertura minima dei vertici.


Da dove viene quel grafico da sinistra? Scusa, non capisco bene la tua spiegazione!
ryyst

1
@ryyst: quel grafico da sinistra è solo un esempio di un grafico planare. Viene utilizzato per dimostrare come trasformare qualsiasi grafo planare di grado fino a 4 nel grafo allineato alla griglia e quindi nella matrice n * m. Un algoritmo di "lancio di bombe" applicato a questa matrice risolverà il problema della copertura dei vertici per questo grafo trasformato e quindi per quel grafo "sinistro".
Evgeny Kluev

Ah, ora capisco, e credo che la tua trasformazione sia corretta. Grazie!
Ryyst

@EvgenyKluev, penso che ora devi dimostrare che la copertura del vertice è ancora NP-difficile per "grafici planari di grado fino a 4".
Shahbaz

@ Shahbaz: temo che questa dimostrazione sarebbe troppo lunga. Quindi ho aggiunto il collegamento alla prova.
Evgeny Kluev

9

È possibile rappresentare questo problema come problema di programmazione intera . (questa è solo una delle possibili soluzioni per affrontare questo problema)

Avere punti:

a b c d
e f g h
i j k l
m n o p

si possono scrivere 16 equazioni dove per esempio vale il punto f

f <= ai + bi + ci + ei + fi + gi + ii + ji + ki   

minimizzato sulla somma di tutti gli indici e sulla soluzione intera.

La soluzione è ovviamente la somma di questi indici.

Questo può essere ulteriormente semplificato impostando tutti gli xi sui limiti 0, così si finisce per avere un'equazione 4 + 1 in questo esempio.

Il problema è che non esiste un banale algoritmo per risolvere tali problemi. Non sono esperto in questo, ma risolvere questo problema come programmazione lineare è NP difficile.


8
Tutti i problemi in NP possono essere formulati come problemi di programmazione intera, quindi questo non è molto utile, a meno che non sappiamo già che il problema è NP-Complete
BlueRaja - Danny Pflughoeft

1
Sono d'accordo. Inoltre non è necessario conoscere le mosse esatte che devono essere eseguite per sapere quale sia la soluzione.
Luka Rahne

1
Quando imposti il ​​limite a 0, il numero di disuguaglianze è ancora 16.
darksky

9

Questa è una risposta parziale, sto cercando di trovare un limite inferiore e un limite superiore che potrebbero essere il numero possibile di bombe.

Nella scheda 3x3 e più piccola, la soluzione è banalmente sempre la cella numerata più grande.

Nelle tavole più grandi di 4x4, il primo evidente limite inferiore è la somma degli angoli:

*2* 3  7 *1*
 1  5  6  2
 2  1  3  2
*6* 9  6 *4*

comunque disponi la bomba, è impossibile cancellare questo tabellone 4x4 con meno di 2 + 1 + 6 + 4 = 13 bombe.

È stato detto in altre risposte che posizionare la bomba sul secondo per eliminare l'angolo non è mai peggio che posizionare la bomba sull'angolo stesso, quindi dato il tabellone:

*2* 3  4  7 *1*
 1  5  2  6  2
 4  3  4  2  1
 2  1  2  4  1
 3  1  3  4  1
 2  1  4  3  2
*6* 9  1  6 *4*

Possiamo azzerare gli angoli posizionando bombe sul secondo angolo per creare un nuovo tabellone:

 0  1  1  6  0
 0  3  0  5  1
 2  1  1  1  0
 2  1  2  4  1
 0  0  0  0  0
 0  0  0  0  0
 0  3  0  2  0

Fin qui tutto bene. Abbiamo bisogno di 13 bombe per liberare gli angoli.

Ora osserva i numeri 6, 4, 3 e 2 contrassegnati di seguito:

 0  1  1 *6* 0
 0  3  0  5  1
 2  1  1  1  0
*2* 1  2 *4* 1
 0  0  0  0  0
 0  0  0  0  0
 0 *3* 0  2  0

Non c'è modo di bombardare due di quelle celle usando una singola bomba, quindi la bomba minima è aumentata di 6 + 4 + 3 + 2, quindi aggiungendo al numero di bombe che abbiamo usato per liberare gli angoli, otteniamo il minimo il numero di bombe richieste per questa mappa è diventato 28 bombe. È impossibile cancellare questa mappa con meno di 28 bombe, questo è il limite inferiore per questa mappa.

È possibile utilizzare un algoritmo avido per stabilire un limite superiore. Altre risposte hanno dimostrato che un algoritmo avido produce una soluzione che utilizza 28 bombe. Poiché abbiamo dimostrato in precedenza che nessuna soluzione ottimale può contenere meno di 28 bombe, 28 bombe sono davvero una soluzione ottimale.

Quando avido e il metodo per trovare il limite minimo che ho menzionato sopra non convergono, però, immagino che devi tornare a controllare tutte le combinazioni.

L'algoritmo per trovare il limite inferiore è il seguente:

  1. Scegli un elemento con il numero più alto, chiamalo P.
  2. Contrassegna tutte le celle a due passi da P e P stesso come non selezionabili.
  3. Aggiungi P minimumsall'elenco.
  4. Ripeti al passaggio 1 finché tutte le celle non sono selezionabili.
  5. Somma l' minimumselenco per ottenere il limite inferiore.

9

Questo sarebbe un approccio avido:

  1. Calcola una matrice di "punteggio" di ordine n X m, dove il punteggio [i] [j] è la detrazione totale di punti nella matrice se la posizione (i, j) è bombardata. (Il punteggio massimo di un punto è 9 e il punteggio minimo è 0)

  2. Spostandoti in senso saggio, trova e scegli la prima posizione con il punteggio più alto (ad esempio (i, j)).

  3. Bomba (i, j). Aumenta il numero di bombe.

  4. Se tutti gli elementi della matrice originale non sono zero, allora vai a 1.

Ho i miei dubbi però che questa sia la soluzione ottimale.

Modificare:

L'approccio Greedy che ho postato sopra, sebbene funzioni, molto probabilmente non ci fornisce la soluzione ottimale. Quindi ho pensato di aggiungere alcuni elementi di DP.

Penso che possiamo essere d'accordo sul fatto che in qualsiasi momento, una delle posizioni con il "punteggio" più alto (punteggio [i] [j] = detrazione totale di punti se (i, j) viene bombardata) deve essere presa di mira. Partendo da questo presupposto, ecco il nuovo approccio:

NumOfBombs (M): (restituisce il numero minimo di bombe necessarie)

  1. Data una matrice M di ordine n X m. Se tutti gli elementi di M sono zero, restituisce 0.

  2. Calcola la matrice "score" M.

    Siano le k posizioni distinte P1, P2, ... Pk (1 <= k <= n * m) le posizioni in M ​​con i punteggi più alti.

  3. ritorno (1 + min (NumOfBombs (M1), NumOfBombs (M2), ..., NumOfBombs (Mk)))

    dove M1, M2, ..., Mk sono le matrici risultanti se bombardiamo rispettivamente le posizioni P1, P2, ..., Pk.

Inoltre, se oltre a questo vogliamo che l'ordine delle posizioni venga bombardato, dovremmo tenere traccia dei risultati di "min".


3
Mi chiedo se impostare il punteggio in modo che sia la somma dei valori correnti produrrebbe risultati migliori. Ciò essenzialmente appiattirebbe il terreno in modo più efficiente.
Eugene

@ Eugene: punto molto interessante. Non riesco a pensare a un motivo per cui la tua strada non dovrebbe produrre risultati migliori ...
SidR

@Eugene: Forse la somma dei valori correnti nelle vicinanze potrebbe essere utilizzata per la misura "prioritaria"? Nuke il nodo con il punteggio più alto e la priorità più alta ..
SidR

Basta leggere questa risposta, penso che sia simile alla seconda risposta che ho appena pubblicato (forse spiegata un po 'di più nella mia risposta). Penso che sarebbe ottimale se ci fosse sempre un solo spazio con il punteggio massimo, perché ti sarebbe garantito che ogni bombardamento ha il massimo effetto possibile. L' ordine dei bombardamenti non ha importanza, quindi scegliere il migliore in ogni fase dovrebbe essere ottimale. Ma poiché potrebbero esserci dei pareggi per il "meglio", forse per una soluzione ottimale dovresti tornare indietro e provare entrambi quando c'è un pareggio.
Tim Goodman

1
@ Eugene, forse non ti sto seguendo. Qual è la differenza tra la riduzione maggiore e la somma più piccola di tutti i valori rimanenti? La somma dei valori rimanenti (dopo il bombardamento) è solo il valore totale corrente meno la riduzione dal bombardamento di quello spazio, quindi non sono equivalenti?
Tim Goodman

8

Il tuo nuovo problema, con i valori non decrescenti tra le righe, è abbastanza facile da risolvere.

Osserva che la colonna di sinistra contiene i numeri più alti. Pertanto, qualsiasi soluzione ottimale deve prima ridurre questa colonna a zero. Quindi, possiamo eseguire un bombardamento 1-D su questa colonna, riducendo a zero ogni elemento in essa contenuto. Lasciamo cadere le bombe sulla seconda colonna in modo che causino il massimo danno. Ci sono molti post qui che trattano del caso 1D, penso, quindi mi sento sicuro nel saltare quel caso. (Se vuoi che lo descriva, posso.). A causa della proprietà decrescente, le tre colonne più a sinistra verranno tutte ridotte a zero. Ma, è possibile che qui utilizzeremo un numero minimo di bombe perché la colonna di sinistra deve essere azzerata.

Ora, una volta azzerata la colonna di sinistra, tagliamo semplicemente le tre colonne più a sinistra che sono ora azzerate e ripetiamo con la matrice ora ridotta. Questo deve fornirci una soluzione ottimale poiché in ogni fase utilizziamo un numero minimo di bombe comprovato.


Capisco. Ho pensato a un'idea simile. : S La prossima volta leggerò con più attenzione. Ma grazie a questo molte persone hanno problemi "carini" da risolvere ".
abc

4

Mathematica Integer Linear Programming utilizzando branch-and-bound

Come è già stato detto, questo problema può essere risolto utilizzando la programmazione lineare intera (che è NP-Hard ). Mathematica ha già ILP integrato. "To solve an integer linear programming problem Mathematica first solves the equational constraints, reducing the problem to one containing inequality constraints only. Then it uses lattice reduction techniques to put the inequality system in a simpler form. Finally, it solves the simplified optimization problem using a branch-and-bound method."[Vedi Tutorial sull'ottimizzazione vincolata in Mathematica ..]

Ho scritto il seguente codice che utilizza le librerie ILP di Mathematica. È sorprendentemente veloce.

solveMatrixBombProblem[problem_, r_, c_] := 
 Module[{}, 
  bombEffect[x_, y_, m_, n_] := 
   Table[If[(i == x || i == x - 1 || i == x + 1) && (j == y || 
        j == y - 1 || j == y + 1), 1, 0], {i, 1, m}, {j, 1, n}];
  bombMatrix[m_, n_] := 
   Transpose[
    Table[Table[
      Part[bombEffect[(i - Mod[i, n])/n + 1, Mod[i, n] + 1, m, 
        n], (j - Mod[j, n])/n + 1, Mod[j, n] + 1], {j, 0, 
       m*n - 1}], {i, 0, m*n - 1}]];
  X := x /@ Range[c*r];
  sol = Minimize[{Total[X], 
     And @@ Thread[bombMatrix[r, c].X >= problem] && 
      And @@ Thread[X >= 0] && Total[X] <= 10^100 && 
      Element[X, Integers]}, X];
  Print["Minimum required bombs = ", sol[[1]]];
  Print["A possible solution = ", 
   MatrixForm[
    Table[x[c*i + j + 1] /. sol[[2]], {i, 0, r - 1}, {j, 0, 
      c - 1}]]];]

Per l'esempio fornito nel problema:

solveMatrixBombProblem[{2, 3, 4, 7, 1, 1, 5, 2, 6, 2, 4, 3, 4, 2, 1, 2, 1, 2, 4, 1, 3, 1, 3, 4, 1, 2, 1, 4, 3, 2, 6, 9, 1, 6, 4}, 7, 5]

Uscite

inserisci qui la descrizione dell'immagine

Per chiunque legga questo con un algoritmo avido

Prova il tuo codice sul seguente problema 10x10:

5   20  7   1   9   8   19  16  11  3  
17  8   15  17  12  4   5   16  8   18  
4   19  12  11  9   7   4   15  14  6  
17  20  4   9   19  8   17  2   10  8  
3   9   10  13  8   9   12  12  6   18  
16  16  2   10  7   12  17  11  4   15  
11  1   15  1   5   11  3   12  8   3  
7   11  16  19  17  11  20  2   5   19  
5   18  2   17  7   14  19  11  1   6  
13  20  8   4   15  10  19  5   11  12

Qui è separato da virgole:

5, 20, 7, 1, 9, 8, 19, 16, 11, 3, 17, 8, 15, 17, 12, 4, 5, 16, 8, 18, 4, 19, 12, 11, 9, 7, 4, 15, 14, 6, 17, 20, 4, 9, 19, 8, 17, 2, 10, 8, 3, 9, 10, 13, 8, 9, 12, 12, 6, 18, 16, 16, 2, 10, 7, 12, 17, 11, 4, 15, 11, 1, 15, 1, 5, 11, 3, 12, 8, 3, 7, 11, 16, 19, 17, 11, 20, 2, 5, 19, 5, 18, 2, 17, 7, 14, 19, 11, 1, 6, 13, 20, 8, 4, 15, 10, 19, 5, 11, 12

Per questo problema, la mia soluzione contiene 208 bombe. Ecco una possibile soluzione (sono riuscito a risolverlo in circa 12 secondi).

inserisci qui la descrizione dell'immagine

Come modo per testare i risultati che Mathematica sta producendo, vedi se il tuo algoritmo avido può fare di meglio.


Sono stato in grado di farlo nel 219 con questa risposta: stackoverflow.com/questions/15300149/bomb-dropping-algorithm/…
Anthony Queen

3

Non è necessario trasformare il problema in problemi secondari lineari.

Usa invece una semplice euristica avida, che è quella di bombardare gli angoli , iniziando da quello più grande.

Nell'esempio fornito ci sono quattro angoli, {2, 1, 6, 4}. Per ogni angolo non c'è mossa migliore che bombardare la cella diagonalmente all'angolo, quindi sappiamo per certo che i nostri primi 2 + 1 + 6 + 4 = 13 bombardamenti devono essere in queste celle diagonali. Dopo aver fatto il bombardamento ci ritroviamo con una nuova matrice:

2 3 4 7 1      0 1 1 6 0      0 1 1 6 0     1 1 6 0     0 0 5     0 0 0 
1 5 2 6 2      0 3 0 5 1      0 3 0 5 1  => 1 0 4 0  => 0 0 3  => 0 0 0  
4 3 4 2 1      2 1 1 1 0      2 1 1 1 0     0 0 0 0     0 0 0     0 0 3  
2 1 2 4 1  =>  2 1 2 4 1  =>  2 1 2 4 1     0 0 3 0     0 0 3      
3 1 3 4 1      0 0 0 0 0      0 0 0 0 0 
2 1 4 3 2      0 0 0 0 0      0 0 0 0 0 
6 9 1 6 4      0 3 0 2 0      0 0 0 0 0 

Dopo i primi 13 bombardamenti usiamo l'euristica per eliminare 3 0 2 tramite tre bombardamenti. Ora abbiamo 2 nuovi angoli, {2, 1} nella quarta riga. Bombardiamo quelli, altri 3 attentati. Abbiamo ridotto la matrice a 4 x 4 ora. C'è un angolo, in alto a sinistra. Lo bombardiamo. Ora abbiamo 2 angoli rimasti, {5, 3}. Poiché il 5 è l'angolo più grande, bombardiamo prima quello, 5 bombardamenti, poi infine bombardiamo il 3 nell'altro angolo. Il totale è 13 + 3 + 3 + 1 + 5 + 3 = 28.


1
Non capisco cosa fai in generale dopo aver bombardato gli angoli
RiaD

Bombardare l'angolo non è mai più efficace del bombardare diagonalmente verso l'interno dall'angolo.
psr

1
psr hai frainteso il mio post, IO STO bombardando in diagonale dall'angolo, rileggi il post
Tyler Durden

11
@TylerDurden: funziona solo perché la matrice è piccola. Su matrici più grandi, dopo aver bombardato l'angolo, generalmente non saresti più in grado di tagliare i bordi.
Lie Ryan

3

Questo fa una ricerca ampia del percorso più breve (una serie di bombardamenti) attraverso questo "labirinto" di posizioni. No, non posso provare che non esiste un algoritmo più veloce, mi dispiace.

#!/usr/bin/env python

M = ((1,2,3,4),
     (2,3,4,5),
     (5,2,7,4),
     (2,3,5,8))

def eachPossibleMove(m):
  for y in range(1, len(m)-1):
    for x in range(1, len(m[0])-1):
      if (0 == m[y-1][x-1] == m[y-1][x] == m[y-1][x+1] ==
               m[y][x-1]   == m[y][x]   == m[y][x+1] ==
               m[y+1][x-1] == m[y+1][x] == m[y+1][x+1]):
        continue
      yield x, y

def bomb(m, (mx, my)):
  return tuple(tuple(max(0, m[y][x]-1)
      if mx-1 <= x <= mx+1 and my-1 <= y <= my+1
      else m[y][x]
      for x in range(len(m[y])))
    for y in range(len(m)))

def findFirstSolution(m, path=[]):
#  print path
#  print m
  if sum(map(sum, m)) == 0:  # empty?
    return path
  for move in eachPossibleMove(m):
    return findFirstSolution(bomb(m, move), path + [ move ])

def findShortestSolution(m):
  black = {}
  nextWhite = { m: [] }
  while nextWhite:
    white = nextWhite
    nextWhite = {}
    for position, path in white.iteritems():
      for move in eachPossibleMove(position):
        nextPosition = bomb(position, move)
        nextPath = path + [ move ]
        if sum(map(sum, nextPosition)) == 0:  # empty?
          return nextPath
        if nextPosition in black or nextPosition in white:
          continue  # ignore, found that one before
        nextWhite[nextPosition] = nextPath

def main(argv):
  if argv[1] == 'first':
    print findFirstSolution(M)
  elif argv[1] == 'shortest':
    print findShortestSolution(M)
  else:
    raise NotImplementedError(argv[1])

if __name__ == '__main__':
  import sys
  sys.exit(main(sys.argv))

1
Questo algoritmo si trova il minor numero di mosse, ma potrebbe richiedere un tempo molto lungo. Hai eseguito questo sul set di dati fornito? Ciò fornirebbe una linea di base per altri algoritmi da confrontare.
Ryan Amos

1
Un sottoinsieme di 5x4 della matrice data è stato risolto in circa 2 secondi, 5x5 richiedeva già più di 2 minuti. Non ho ancora provato di più ;-) Sì, questo algoritmo non è ottimizzato per nient'altro che il compito originale: trova la soluzione più breve.
Alfe

2
Questa è la bellezza della complessità esponenziale.
Ryan Amos

3

Sembra che un approccio di programmazione lineare possa essere molto utile qui.

Sia P m xn la matrice con i valori delle posizioni:

Matrice delle posizioni

Ora definiamo una matrice bomba B (x, y) mxn , con 1 ≤ x ≤ m , 1 ≤ y ≤ n come sotto

Matrice bomba

in modo tale da

Valori delle posizioni nella matrice della bomba

Per esempio:

B (3, 3)

Quindi stiamo cercando una matrice B m xn = [ b ij ] quella

  1. Può essere definito come una somma di matrici di bombe:

    B come somma delle matrici delle bombe

    ( q ij sarebbe quindi la quantità di bombe che lasceremmo cadere nella posizione p ij )

  2. p ij - b ij ≤ 0 (per essere più succinti, diciamolo come P - B ≤ 0 )

Inoltre, B dovrebbe ridurre al minimo la sommasomma delle quantità di bombe .

Possiamo anche scrivere B come la brutta matrice avanti:

B come matrice di somma di quantità

e poiché P - B ≤ 0 (che significa P ≤ B ) abbiamo il seguente sistema di disuguaglianza piuttosto lineare di seguito:

Relazione tra numero di bombe sganciate e valori nelle posizioni

Essendo q mn x 1 definito come

Vettore di quantità

p mn x 1 definito come

Valori di P distribuiti come vettore

Possiamo dire di avere un sistema Il sistema di seguito rappresentato come prodotto di matrici http://latex.codecogs.com/gif.download?S%5Cmathbf%7Bq%7D&space;%5Cge&space;%5Cmathbf%7Bp%7D essendo S mn x mn la matrice da invertire per risolvere il sistema. Non l'ho ampliato da solo, ma credo che dovrebbe essere facile farlo in codice.

Ora, abbiamo un problema minimo che può essere affermato come

Il sistema che dobbiamo risolvere

Credo che sia qualcosa di facile, quasi banale da risolvere con qualcosa come l' algoritmo simplex (c'è questo documento piuttosto interessante a riguardo ). Tuttavia, non conosco quasi nessuna programmazione lineare (farò un corso a riguardo su Coursera ma è solo in futuro ...), ho avuto qualche grattacapo cercando di capirlo e ho un enorme lavoro da freelance da finire quindi ho arrenditi qui. Può essere che ho fatto qualcosa di sbagliato ad un certo punto, o che non si può andare oltre, ma credo che questo percorso alla fine può portare alla soluzione. Ad ogni modo, sono ansioso per il tuo feedback.

(Un ringraziamento speciale per questo fantastico sito per creare immagini da espressioni LaTeX )


Sei sicuro che le tue disuguaglianze non siano annullate? Questo è Sq> = P? ovvero, il numero totale di volte in cui un quadrato viene bombardato è maggiore o uguale alla matrice data.
darksky

1
Quando le variabili di un programma lineare sono vincolate a numeri interi, la chiamiamo "programmazione lineare intera" (IP). A differenza del caso continuo, IP è NP-Complete. Sfortunatamente, l'algoritmo simplex non aiuta, a meno che un'approssimazione non sia accettabile. E l'IP è già stato menzionato in un'altra risposta .
BlueRaja - Danny Pflughoeft

@ BlueRaja-DannyPflughoeft corretto. "Despite the many crucial applications of this problem, and intense interest by researchers, no efficient algorithm is known for it.vedere pagina 254. La programmazione lineare intera è un problema computazionale molto difficile. La nostra unica speranza di essere efficienti è sfruttare le proprietà intrinseche della tua matrice S. Dopotutto, non è così arbitrario.
darksky

3

Questa golosa soluzione sembra essere corretta :

Come indicato nei commenti, fallirà in 2D. Ma forse potresti migliorarlo.

Per 1D:
se ci sono almeno 2 numeri non è necessario sparare a quello più a sinistra perché sparare al secondo non è peggio . Quindi spara al secondo, mentre il primo non è 0, perché devi farlo. Passa alla cella successiva. Non dimenticare l'ultima cella.

Codice C ++:

void bombs(vector<int>& v, int i, int n){
    ans += n;
    v[i] -= n;
    if(i > 0)
        v[i - 1] -= n;
    if(i + 1< v.size())
        v[i + 1] -= n;
}

void solve(vector<int> v){
    int n = v.size();
    for(int i = 0; i < n;++i){
        if(i != n - 1){
            bombs(v, i + 1, v[i]);
        }
        else
            bombs(v, i, v[i])
    }
}

Quindi per il 2D:
Ancora: non è necessario sparare nella prima riga (se c'è la seconda). Quindi spara al secondo. Risolvi l'attività 1D per la prima riga. (perché devi renderlo nullo). Scendere. Non dimenticare l'ultima riga.


5
Un controesempio: "0110","1110","1110". Hai solo bisogno di 1 colpo, ma credo che il tuo algoritmo ne userebbe 2.
maniek

2

Per ridurre al minimo il numero di bombe, dobbiamo massimizzare l'effetto di ogni bomba. Per ottenere ciò, in ogni fase dobbiamo selezionare il miglior target. Per ogni punto che lo somma e sono otto vicini, potrebbe essere usato come quantità di efficienza per bombardare questo punto. Ciò fornirà una sequenza di bombe quasi ottimale.

UPD : Dovremmo anche prendere in considerazione il numero di zeri, perché bombardarli è inefficiente. In effetti il ​​problema è ridurre al minimo il numero di zeri colpiti. Ma non possiamo sapere come ogni passo ci avvicini a questo obiettivo. Concordo con l'idea che il problema sia NP-completo. Suggerisco un approccio avido, che darà una risposta quasi reale.


Questo non è ottimale. Contro-esempio: 1010101, 0010100(riga superiore, fila inferiore) il suo approccio richiederà 3. Può essere fatto in 2.
Mysticial

2

Credo che per ridurre al minimo la quantità di bombe devi semplicemente massimizzare la quantità di danni .. perché ciò accada devi controllare l'area che ha la forza più forte .. quindi prima analizzi il campo con un kernel 3x3 e controlli dove si somma è più forte .. e bomba lì .. e fallo finché il campo non è piatto .. per questo file la risposta è 28

var oMatrix = [
[2,3,4,7,1],
[1,5,2,6,2],
[4,3,4,2,1],
[2,1,2,4,1],
[3,1,3,4,1],
[2,1,4,3,2],
[6,9,1,6,4]
]

var nBombs = 0;
do
{
    var bSpacesLeftToBomb = false;
    var nHigh = 0;
    var nCellX = 0;
    var nCellY = 0;
    for(var y = 1 ; y<oMatrix.length-1;y++) 
        for(var x = 1 ; x<oMatrix[y].length-1;x++)  
        {
            var nValue = 0;
            for(var yy = y-1;yy<=y+1;yy++)
                for(var xx = x-1;xx<=x+1;xx++)
                    nValue += oMatrix[yy][xx];

            if(nValue>nHigh)
            {
                nHigh = nValue;
                nCellX = x;
                nCellY = y; 
            }

        }
    if(nHigh>0)
    {
        nBombs++;

        for(var yy = nCellY-1;yy<=nCellY+1;yy++)
        {
            for(var xx = nCellX-1;xx<=nCellX+1;xx++)
            {
                if(oMatrix[yy][xx]<=0)
                    continue;
                oMatrix[yy][xx] = --oMatrix[yy][xx];
            }
        }
        bSpacesLeftToBomb = true;
    }
}
while(bSpacesLeftToBomb);

alert(nBombs+'bombs');

Questo è lo stesso algoritmo di alcune delle altre risposte, ma molto più tardi.
psr

@psr Non solo quello. Non è ottimale.
Mysticial

L'ho postato, perché, mentre questo algoritmo è stato proposto, non ho trovato alcun post di codice o "prof of concept". quindi ho pensato che potesse aiutare la discution .. ma .. btw @Mysticial hai prof che c'è un modo più ottimale?
CaldasGSM

@CaldasGSM Nessun problema, il problema originale (senza la sequenza) è difficile. Finora c'è solo una risposta che lo risolve in modo ottimale, ma funziona in tempo esponenziale.
Mysticial

2

Ecco una soluzione che generalizza le buone proprietà degli angoli.

Supponiamo di poter trovare un punto di rilascio perfetto per un dato campo, ovvero un modo migliore per diminuire il valore in esso. Quindi, per trovare il numero minimo di bombe da sganciare, potrebbe essere una prima bozza di un algoritmo (il codice viene copiato e incollato da un'implementazione di ruby):

dropped_bomb_count = 0
while there_are_cells_with_non_zero_count_left
  coordinates = choose_a_perfect_drop_point
  drop_bomb(coordinates)
  dropped_bomb_count += 1
end
return dropped_bomb_count

La sfida è choose_a_perfect_drop_point. Per prima cosa, definiamo cos'è un punto di caduta perfetto.

  • Un punto di rilascio per (x, y)diminuisce il valore in (x, y). Può anche diminuire i valori in altre celle.
  • Un punto di rilascio a per (x, y)è migliore di un punto di rilascio b perché (x, y)se diminuisce i valori in un corretto superset di celle che b diminuisce.
  • Un punto di caduta è massimo se non esiste un altro punto di caduta migliore.
  • Due punti di rilascio per (x, y)sono equivalenti se riducono lo stesso insieme di celle.
  • Un punto di caduta per (x, y)è perfetto se è equivalente a tutti i punti di caduta massimi per (x, y).

Se esiste un punto di rilascio perfetto per (x, y), non è possibile diminuire il valore in modo (x, y)più efficace che sganciare una bomba su uno dei punti di rilascio perfetti per (x, y).

Un punto di rilascio perfetto per un dato campo è un punto di rilascio perfetto per una qualsiasi delle sue celle.

Alcuni esempi:

1 0 1 0 0
0 0 0 0 0
1 0 0 0 0
0 0 0 0 0
0 0 0 0 0

Il punto di rilascio perfetto per la cella (0, 0)(indice a base zero) è (1, 1). Tutti gli altri punti di caduta per (1, 1), che è (0, 0), (0, 1)e (1, 0), diminuiscono meno cellule.

0 0 0 0 0
0 0 0 0 0
0 0 1 0 0
0 0 0 0 0
0 0 0 0 0

Un punto di goccia perfetto per la cella (2, 2)(indice a base zero) è (2, 2), e anche tutte le cellule circostanti (1, 1), (1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2), e (3, 3).

0 0 0 0 1
0 0 0 0 0
0 0 1 0 0
0 0 0 0 0
0 0 0 0 0

un punto di rilascio perfetto per la cella (2, 2)è (3, 1): diminuisce il valore in (2, 2)e il valore in (4, 0). Tutti gli altri punti di rilascio per (2, 2)non sono massimi, poiché diminuiscono di una cella in meno. Il punto di caduta perfetto per (2, 2)è anche il punto di caduta perfetto per (4, 0), ed è l'unico punto di caduta perfetto per il campo. Porta alla soluzione perfetta per questo campo (una bomba).

1 0 0 0 0
0 0 0 0 0
0 0 1 0 0
0 0 0 0 0
1 0 0 0 0

Non esiste un punto di rilascio perfetto per (2, 2): Sia (1, 1)e (1, 3)decremento (2, 2)che un'altra cella (sono i punti di rilascio massimi per (2, 2)), ma non sono equivalenti. Tuttavia, (1, 1)è un punto di caduta perfetto per (0, 0)ed (1, 3)è un punto di caduta perfetto per (0, 4).

Con questa definizione di punti di caduta perfetti e un certo ordine di controlli, ottengo il seguente risultato per l'esempio nella domanda:

Drop bomb on 1, 1
Drop bomb on 1, 1
Drop bomb on 1, 5
Drop bomb on 1, 5
Drop bomb on 1, 5
Drop bomb on 1, 6
Drop bomb on 1, 2
Drop bomb on 1, 2
Drop bomb on 0, 6
Drop bomb on 0, 6
Drop bomb on 2, 1
Drop bomb on 2, 5
Drop bomb on 2, 5
Drop bomb on 2, 5
Drop bomb on 3, 1
Drop bomb on 3, 0
Drop bomb on 3, 0
Drop bomb on 3, 0
Drop bomb on 3, 0
Drop bomb on 3, 0
Drop bomb on 3, 4
Drop bomb on 3, 4
Drop bomb on 3, 3
Drop bomb on 3, 3
Drop bomb on 3, 6
Drop bomb on 3, 6
Drop bomb on 3, 6
Drop bomb on 4, 6
28

Tuttavia, l'algoritmo funziona solo se è presente almeno un punto di rilascio perfetto dopo ogni passaggio. È possibile costruire esempi in cui non ci sono punti di caduta perfetti:

0 1 1 0
1 0 0 1
1 0 0 1
0 1 1 0

Per questi casi, possiamo modificare l'algoritmo in modo che invece di un punto di caduta perfetto, scegliamo una coordinata con una scelta minima di punti di caduta massimi, quindi calcoliamo il minimo per ogni scelta. Nel caso precedente, tutte le celle con valori hanno due punti di rilascio massimi. Ad esempio, (0, 1)ha i punti di caduta massimi (1, 1)e (1, 2). Scegliere uno dei due e poi calcolare il minimo porta a questo risultato:

Drop bomb on 1, 1
Drop bomb on 2, 2
Drop bomb on 1, 2
Drop bomb on 2, 1
2

Questo è più o meno l'algoritmo avido presentato sopra.
darksky

Bene, è anche un algoritmo avido, ma invece di concentrarmi su angoli e bordi, ho definito come scegliere il punto di rilascio successivo. Con il quadrato di esempio di 5x7, è facile parlare di angoli, su un campo 1000x1000, non tanto. Se controlli l'ordine in cui il mio algoritmo cancella il campo, non è dall'esterno verso l'interno, ma dall'alto verso il basso / da sinistra a destra.
Tammo Freese

2

Ecco un'altra idea:

Cominciamo assegnando un peso a ogni spazio sul tabellone per quanti numeri verrebbero ridotti facendo cadere una bomba lì. Quindi, se lo spazio ha un numero diverso da zero, ottiene un punto e se uno spazio adiacente ha un numero diverso da zero, ottiene un punto aggiuntivo. Quindi, se c'è una griglia 1000 per 1000, abbiamo un peso assegnato a ciascuno dei 1 milione di spazi.

Quindi ordina l'elenco degli spazi in base al peso e bomba quello con il peso più alto. Questo sta ottenendo il massimo profitto, per così dire.

Successivamente, aggiorna il peso di ogni spazio il cui peso è influenzato dalla bomba. Questo sarà lo spazio che hai bombardato, qualsiasi spazio immediatamente adiacente e qualsiasi spazio immediatamente adiacente a quelli. In altre parole, qualsiasi spazio che avrebbe potuto avere il suo valore ridotto a zero dal bombardamento, o il valore di uno spazio vicino ridotto a zero.

Quindi, riordina gli spazi dell'elenco in base al peso. Dal momento che solo un piccolo sottoinsieme di spazi è stato modificato dal bombardamento, non sarà necessario ricorrere all'intero elenco, basta spostare quelli nell'elenco.

Bombardate il nuovo spazio di peso più alto e ripetete la procedura.

Questo garantisce che ogni bombardamento riduca quanti più spazi possibile (in pratica colpisce il minor numero possibile di spazi che sono già zero), quindi sarebbe ottimale, tranne che possono essere legami in pesi. Quindi potresti dover fare un po 'di back tracking quando c'è un pareggio per il peso superiore. Tuttavia, solo un pareggio per il peso massimo conta, non altri legami, quindi si spera che non sia troppo indietro.

Modifica: il controesempio di Mysticial di seguito dimostra che in realtà non è garantito che sia ottimale, indipendentemente dai legami di peso. In alcuni casi ridurre il peso il più possibile in un dato passaggio lascia effettivamente le bombe rimanenti troppo sparse per ottenere una riduzione cumulativa dopo il secondo passaggio come si potrebbe avere con una scelta leggermente meno avida nel primo passaggio. Sono stato in qualche modo fuorviato dall'idea che i risultati siano insensibili all'ordine dei bombardamenti. Essi sonoinsensibile all'ordine in quanto potresti prendere qualsiasi serie di bombardamenti e riprodurli dall'inizio in un ordine diverso e finire con lo stesso tabellone risultante. Ma non ne consegue che si possa considerare ogni attentato in modo indipendente. O, almeno, ogni bombardamento deve essere considerato in un modo che tenga conto di quanto sia ben impostato il tabellone per i bombardamenti successivi.


sarà ancora molto backtracking, all'inizio poiché i campi hanno pochissimo zero, i pesi della maggior parte delle celle saranno tutti nove.
Lie Ryan

Sì, questo è un buon punto, dal momento che non c'è una vasta gamma di pesi possibili (solo da 0 a 9).
Tim Goodman

Non sono ancora sicuro al 100% di quanto sia necessario il backtracking ... potrebbe essere istruttivo costruire una griglia in cui una scelta di bombardamento avido è inferiore a un'altra scelta di bombardamento avido. Forse c'è un modo coerente per anticipare quale è meglio.
Tim Goodman

In realtà, vedo che il colonnello Panic ha fatto questo nella sua risposta. Il motivo per cui una scelta avida può essere migliore di un'altra è che si lascia i numeri rimanenti più distribuiti.
Tim Goodman

3
1010101, 0010100potrebbe essere un controesempio che dimostra che questo approccio non è ottimale. Questo approccio richiede 3. Può essere fatto in 2.
Mysticial

1

Bene, supponiamo di numerare le posizioni della scacchiera 1, 2, ..., nx m. Qualsiasi sequenza di lancio di bombe può essere rappresentata da una sequenza di numeri in questo set, dove i numeri possono ripetersi. Tuttavia, l'effetto sul tabellone è lo stesso indipendentemente dall'ordine in cui sganci le bombe, quindi in realtà qualsiasi scelta di lancio di bombe può essere rappresentata come un elenco di numeri nxm, dove il primo numero rappresenta il numero di bombe sganciate sulla posizione 1 , il secondo numero rappresenta il numero di bombe sganciate sulla posizione 2, ecc. Chiamiamo "chiave" questo elenco di numeri nxm.

Puoi provare prima a calcolare tutti gli stati del tabellone risultanti da 1 lancio di bombe, quindi usarli per calcolare tutti gli stati del tabellone risultanti da 2 lanci di bombe, ecc. Finché non ottieni tutti zeri. Ma ad ogni passaggio memorizzereste gli stati utilizzando la chiave che ho definito sopra, in modo da poter utilizzare questi risultati nel calcolo del passaggio successivo (un approccio di "programmazione dinamica").

Ma a seconda delle dimensioni di n, m e dei numeri nella griglia, i requisiti di memoria di questo approccio potrebbero essere eccessivi. Puoi buttare via tutti i risultati per le bombe N dopo aver calcolato tutti i risultati per N + 1, quindi ci sono dei risparmi. E ovviamente non è possibile memorizzare nella cache nulla a costo di impiegare molto più tempo: l'approccio di programmazione dinamica scambia la memoria con la velocità.


1
Il dubbio è possibile poiché (se ho capito bene). n = m. Ho bisogno di 10 ^ 6 puntatori int a (10 ^ 6) ^ 2 celle int. Ho tante schede quante sono le chiavi nella tabella. 10 ^ 12 dubbio posso allocare così tanto in una macchina a 32 bit.
abc

Sì, ho appena visto il tuo commento sulle schede che arrivano fino a 1000 per 1000. Quindi questo è un milione di int per lo stato di ogni scheda, più un milione di int per il conteggio delle bombe sganciate su ogni posizione. Quindi per ogni tavola che immagazzini, hai bisogno di 2 milioni di int, e ci sono molte possibili tavole ...
Tim Goodman

Ho aggiunto una seconda risposta che utilizza un approccio diverso.
Tim Goodman

1
Si. Una specie di approccio a forza bruta, ma immagino non molto pratico per una tavola grande.
Tim Goodman

@ Kostek, perché una stima così bassa? È più simile alla memoria k ^ (m * n) con k come limite per i numeri con cui il tabellone è inizialmente riempito.
Rotsor

1

Se vuoi la soluzione ottimale assoluta per pulire la tavola dovrai usare il classico backtracking, ma se la matrice è molto grande ci vorranno anni per trovare la soluzione migliore, se vuoi una soluzione ottimale "possibile" puoi usare l'algoritmo greedy , se hai bisogno di aiuto per scrivere l'algoritmo posso aiutarti

Vieni a pensarci che è il modo migliore. Crea un'altra matrice lì per memorizzare i punti che rimuovi facendo cadere una bomba lì, quindi scegli la cella con il massimo dei punti e sgancia la bomba lì aggiorna la matrice dei punti e continua. Esempio:

2 3 5 -> (2+(1*3)) (3+(1*5)) (5+(1*3))
1 3 2 -> (1+(1*4)) (3+(1*7)) (2+(1*4))
1 0 2 -> (1+(1*2)) (0+(1*5)) (2+(1*2))

valore cella +1 per ogni cella adiacente con un valore maggiore di 0


7
sarà necessario utilizzare backtracking classico . Hai una prova per questo?
Shahbaz

Non ne sono sicuro. È del concorso a cui mi sto preparando (dell'anno precedente). I limiti sono 1 <= n, m <= 1000 (non so se grande o meno). Comunque hai bisogno di una risposta esatta (è simile al concorso CERC e così via). Il limite di tempo non è dato, nessuna risposta, nessuna soluzione sulla pagina del concorso.
abc

beh, ogni altro algoritmo ti darà una possibile soluzione ottimale ma finché non li
proverai

2
non è necessario utilizzare il backtracking perché è la combinazione che si cerca, non la permuta. L'ordine di sganciare le bombe non è importante
Luka Rahne

quindi puoi provare a utilizzare una variazione di avido. ad ogni passo crea una nuova matrice e ogni punto avrà il valore della sua cella + 1 per ogni cella accanto ad essa> 0 in questo modo sceglierà meglio dove sganciare le prossime bombe
cosmin.danisor

1

Forza bruta !

So che non è efficiente, ma anche se trovi un algoritmo più veloce, puoi sempre testare questo risultato per sapere quanto sia accurato.

Usa un po 'di ricorsione, come questo:

void fn(tableState ts, currentlevel cl)
{
  // first check if ts is all zeros yet, if not:
  //
  // do a for loop to go through all cells of ts, 
  // for each cell do a bomb, and then
  // call: 
  // fn(ts, cl + 1);

}

Puoi renderlo più efficiente memorizzando nella cache, se un modo diverso porta allo stesso risultato, non dovresti ripetere gli stessi passaggi.

Elaborare:

se il bombardamento della cella 1,3,5 porta allo stesso risultato del bombardamento della cella 5,3,1, allora, non dovresti ripetere tutti i passaggi successivi di nuovo per entrambi i casi, solo 1 è sufficiente, dovresti memorizzare da qualche parte tutti stati della tabella e utilizzare i suoi risultati.

Un hash delle statistiche della tabella può essere utilizzato per fare un confronto veloce.


1
  1. Non bombardare mai il confine (a meno che il quadrato non abbia un vicino non di confine)
  2. Zero corner.
  3. A zero angolo, rilascia il valore dell'angolo di un quadrato in diagonale (l'unico vicino non di confine)
  4. Questo creerà nuovi angoli. Vai a 2

Modifica: non ho notato che Kostek ha suggerito quasi lo stesso approccio, quindi ora faccio un'affermazione più forte: se gli angoli da cancellare vengono scelti per essere sempre sullo strato più esterno, allora è ottimale.

Nell'esempio di OP: lasciare 2 (come 1 + 1 o 2) su qualsiasi altra cosa che su 5 non porta a colpire qualsiasi casella che cadere su 5 colpirebbe. Quindi dobbiamo semplicemente rilasciare 2 su 5 (e 6 in basso a sinistra 1 ...)

Dopodiché, c'è solo un modo per cancellare (nell'angolo in alto a sinistra) ciò che era originariamente 1 (ora 0), e cioè rilasciando 0 su B3 (Excel come notazione). E così via.

Solo dopo aver cancellato intere colonne A ed E e 1 e 7 righe, inizia a cancellare uno strato più in profondità.

Considera cancellati solo quelli cancellati intenzionalmente, cancellare gli angoli di valore 0 non costa nulla e semplifica il pensiero.

Poiché tutte le bombe sganciate in questo modo devono essere sganciate e questo porta a campi sgomberati, è una soluzione ottimale.


Dopo una buona dormita ho capito che questo non è vero. Prendere in considerazione

  ABCDE    
1 01000
2 10000
3 00000
4 00000

Il mio approccio sarebbe sganciare bombe su B3 e C2, quando sarebbe sufficiente sganciare su B2


Ma questo è ottimale però?
Mysticial

7
I nuovi angoli possono essere bombardati in 2 modi (se la maggior parte degli angoli contiene il più basso di tutti e 4 i valori). Qual è il bombardamento ottimale?
abc

Stavo pensando a un approccio simile, e quando raggiungi una situazione come quella descritta da Kostek, inizia a usare il backtracking ...
Karoly Horvath

Gli angoli ti danno la quantità minima di bombe da sganciare sul quadrato diagonale. Ma una volta azzerati, il riquadro del bordo successivo non avrà necessariamente un punto ottimale ovvio. Tuttavia, è un buon modo per ridurre lo spazio di ricerca.
Eugene

Che ne dici di scegliere la nuova diagonale d'angolo che produce il conteggio totale più alto nella hit box?
Giudice Maygarden

1

Ecco la mia soluzione .. Non lo scriverò ancora in codice poiché non ho tempo, ma credo che questo dovrebbe produrre un numero ottimale di mosse ogni volta, anche se non sono sicuro di quanto sarebbe efficiente nel trovare i punti da bombardare.

In primo luogo, come ha affermato @Luka Rahne in uno dei commenti, l'ordine in cui bombardate non è importante, solo la combinazione.

In secondo luogo, come molti altri hanno affermato, bombardare 1-off la diagonale dagli angoli è ottimale perché tocca più punti degli angoli.

Questo genera le basi per la mia versione dell'algoritmo: possiamo bombardare il '1-off dagli angoli' per primo o per ultimo, non importa (in teoria) bombardiamo quelli per primi perché rende più facili le decisioni successive (in pratica) Bombardiamo il punto che colpisce più punti, bombardando contemporaneamente quegli angoli.

Definiamo i punti di resistenza come i punti sul tabellone con il maggior numero di punti non bombardabili + il maggior numero di 0 intorno a loro

i punti non bombardabili possono essere definiti come punti che non esistono nel nostro attuale ambito del tabellone che stiamo guardando.

Definirò anche 4 limiti che gestiranno il nostro ambito: Top = 0, Left = 0, Bottom = k, right = j. (valori per iniziare)

Infine, definirò bombe ottimali come bombe che vengono sganciate su punti adiacenti a punti di resistenza e toccano (1) il punto di resistenza con il valore più alto e (2) il maggior numero di punti possibile.

Per quanto riguarda l'approccio, è ovvio che stiamo lavorando dall'esterno verso l'interno. Potremo lavorare con 4 "bombardieri" allo stesso tempo.

I primi punti di resistenza sono ovviamente i nostri angoli. I punti "fuori limite" non sono bombardabili (ci sono 5 punti fuori campo per ogni angolo). Quindi bombardiamo i punti in diagonale prima dagli angoli.

Algoritmo:

  1. Trova i 4 punti bomba ottimali.
  2. Se un punto bomba sta bombardando un punto di resistenza che sta toccando 2 limiti (cioè un angolo), bombardare fino a quel punto è 0. Altrimenti, bombardare ciascuno fino a quando uno dei punti di resistenza che toccano il punto bomba ottimale è 0.
  3. per ogni limite: if (somma (limite) == 0) anticipo limite

ripetere fino a che SUPERIORE = INFERIORE e SINISTRA = DESTRA

Proverò a scrivere il codice effettivo più tardi


1

Potresti usare la pianificazione dello spazio statale. Ad esempio, utilizzando A * (o una delle sue varianti) abbinato a un'euristica f = g + hcome questa:

  • g: numero di bombe sganciate finora
  • h: somma di tutti i valori della griglia divisa per 9 (che è il miglior risultato, il che significa che abbiamo un'euristica ammissibile)

1

Ho avuto anche 28 mosse. Ho usato due test per la migliore mossa successiva: prima la mossa che produce la somma minima per la tavola. Secondo, a parità di somma, il movimento che produce la massima densità, definita come:

number-of-zeros / number-of-groups-of-zeros

Questo è Haskell. "Risolvi scheda" mostra la soluzione del motore. Puoi giocare digitando "principale", quindi inserire un punto di destinazione, "migliore" per un consiglio o "esci" per uscire.

OUTPUT:
* Principale> scheda di risoluzione
[(4,4), (3,6), (3,3), (2,2), (2,2), (4,6), (4,6), (2,6), (3,2), (4,2), (2,6), (3,3), (4,3), (2,6), (4,2), (4 , 6), (4,6), (3,6), (2,6), (2,6), (2,4), (2,4), (2,6), (3,6 ), (4,2), (4,2), (4,2), (4,2)]

import Data.List
import Data.List.Split
import Data.Ord
import Data.Function(on)

board = [2,3,4,7,1,
         1,5,2,6,2,
         4,3,4,2,1,
         2,1,2,4,1,
         3,1,3,4,1,
         2,1,4,3,2,
         6,9,1,6,4]

n = 5
m = 7

updateBoard board pt =
  let x = fst pt
      y = snd pt
      precedingLines = replicate ((y-2) * n) 0
      bomb = concat $ replicate (if y == 1
                                    then 2
                                    else min 3 (m+2-y)) (replicate (x-2) 0 
                                                         ++ (if x == 1 
                                                                then [1,1]
                                                                else replicate (min 3 (n+2-x)) 1)
                                                                ++ replicate (n-(x+1)) 0)
  in zipWith (\a b -> max 0 (a-b)) board (precedingLines ++ bomb ++ repeat 0)

showBoard board = 
  let top = "   " ++ (concat $ map (\x -> show x ++ ".") [1..n]) ++ "\n"
      chunks = chunksOf n board
  in putStrLn (top ++ showBoard' chunks "" 1)
       where showBoard' []     str count = str
             showBoard' (x:xs) str count =
               showBoard' xs (str ++ show count ++ "." ++ show x ++ "\n") (count+1)

instances _ [] = 0
instances x (y:ys)
  | x == y    = 1 + instances x ys
  | otherwise = instances x ys

density a = 
  let numZeros = instances 0 a
      groupsOfZeros = filter (\x -> head x == 0) (group a)
  in if null groupsOfZeros then 0 else numZeros / fromIntegral (length groupsOfZeros)

boardDensity board = sum (map density (chunksOf n board))

moves = [(a,b) | a <- [2..n-1], b <- [2..m-1]]               

bestMove board = 
  let lowestSumMoves = take 1 $ groupBy ((==) `on` snd) 
                              $ sortBy (comparing snd) (map (\x -> (x, sum $ updateBoard board x)) (moves))
  in if null lowestSumMoves
        then (0,0)
        else let lowestSumMoves' = map (\x -> fst x) (head lowestSumMoves) 
             in fst $ head $ reverse $ sortBy (comparing snd) 
                (map (\x -> (x, boardDensity $ updateBoard board x)) (lowestSumMoves'))   

solve board = solve' board [] where
  solve' board result
    | sum board == 0 = result
    | otherwise      = 
        let best = bestMove board 
        in solve' (updateBoard board best) (result ++ [best])

main :: IO ()
main = mainLoop board where
  mainLoop board = do 
    putStrLn ""
    showBoard board
    putStr "Pt: "
    a <- getLine
    case a of 
      "quit"    -> do putStrLn ""
                      return ()
      "best"    -> do putStrLn (show $ bestMove board)
                      mainLoop board
      otherwise -> let ws = splitOn "," a
                       pt = (read (head ws), read (last ws))
                   in do mainLoop (updateBoard board pt)

1

Sembra che qui ci sia una sottostruttura corrispondente non bipartita. Considera il seguente esempio:

0010000
1000100
0000001
1000000
0000001
1000100
0010000

La soluzione ottimale per questo caso ha dimensione 5 poiché è la dimensione di una copertura minima dei vertici di un ciclo di 9 per i suoi bordi.

Questo caso, in particolare, mostra che il rilassamento della programmazione lineare che alcune persone hanno pubblicato non è esatto, non funziona e tutte quelle altre brutte cose. Sono abbastanza sicuro di poter ridurre "coprire i vertici del mio grafo cubico planare con il minor numero di spigoli possibile" al tuo problema, il che mi fa dubitare che una qualsiasi delle soluzioni avide / in salita funzionerà.

Non vedo un modo per risolvere questo problema in tempo polinomiale nel peggiore dei casi. Potrebbe esserci una soluzione di ricerca binaria e DP molto intelligente che non vedo.

EDIT : vedo che il concorso ( http://deadline24.pl ) è indipendente dalla lingua; ti inviano un mucchio di file di input e tu invii loro degli output. Quindi non hai bisogno di qualcosa che funzioni nel tempo polinomiale peggiore. In particolare, puoi guardare l'input !

Ci sono un sacco di piccoli casi nell'input. Poi c'è una custodia 10x1000, una custodia 100x100 e una custodia 1000x1000. I tre grandi casi sono tutti molto ben educati. Le voci adiacenti orizzontalmente hanno in genere lo stesso valore. Su una macchina relativamente robusta, sono in grado di risolvere tutti i casi forzando brute utilizzando CPLEX in un paio di minuti. Sono stato fortunato con il 1000x1000; il rilassamento LP sembra avere una soluzione ottima integrale. Le mie soluzioni concordano con i .ansfile forniti nel pacchetto di dati di prova.

Scommetto che puoi usare la struttura dell'input in un modo molto più diretto di quanto ho fatto io se lo guardassi; sembra che tu possa tagliare la prima riga, o due o tre ripetutamente finché non hai più nulla. (Sembra che, nel 1000x1000, tutte le righe non siano in aumento? Immagino sia da lì che proviene la tua "parte B"?)


Sì. A volte salto solo una parte "irrilevante" del testo. Fatti un'idea breve e così via. Questa volta fondamentalmente cambia livello da facile a difficile da morire: P Comunque so che puoi provare a fare un'euristica avendo "conosciuto" l'insieme di input. D'altra parte penso solo che se la risposta non è in punti percentuali, deve esserci un algoritmo che funzionerà facilmente durante 5h. Tutto quello che ho trovato aveva una complessità troppo grande. Poi l'ho letto con più attenzione, quando qualcuno ha chiesto dell'origine :)
abc

Possiamo dire grazie a questo molte persone hanno un bel problema a cui pensare, ma dubitare che si possa fare in tempo polinomiale. È divertente come un semplice vincolo cambi il livello del compito da facile a impossibile.
abc

@ Kostek: Scusa se non sono stato chiaro. Non sono ... piuttosto bravo a lanciare spiegazioni a un livello appropriato per il pubblico. :) Dove non ero chiaro?
tmyklebu

1

Non riesco a pensare a un modo per calcolare il numero effettivo senza solo calcolare la campagna di bombardamenti usando la mia migliore euristica e spero di ottenere un risultato ragionevole.

Quindi il mio metodo è calcolare una metrica di efficienza di bombardamento per ogni cella, bombardare la cella con il valore più alto, .... iterare il processo finché non ho appiattito tutto. Alcuni hanno sostenuto l'uso di un semplice danno potenziale (cioè un punteggio da 0 a 9) come metrica, ma ciò non è sufficiente martellando le celle di alto valore e non facendo uso della sovrapposizione del danno. Calcolerei cell value - sum of all neighbouring cells, reimposterei qualsiasi positivo a 0 e userei il valore assoluto di qualsiasi cosa negativa. Intuitivamente questa metrica dovrebbe fare una selezione che aiuti a massimizzare la sovrapposizione dei danni sulle celle con conteggi elevati invece di martellarle direttamente.

Il codice seguente raggiunge la distruzione totale del campo di prova in 28 bombe (si noti che utilizzando il potenziale danno come metrica si ottiene 31!).

using System;
using System.Collections.Generic;
using System.Linq;

namespace StackOverflow
{
  internal class Program
  {
    // store the battle field as flat array + dimensions
    private static int _width = 5;
    private static int _length = 7;
    private static int[] _field = new int[] {
        2, 3, 4, 7, 1,
        1, 5, 2, 6, 2,
        4, 3, 4, 2, 1,
        2, 1, 2, 4, 1,
        3, 1, 3, 4, 1,
        2, 1, 4, 3, 2,
        6, 9, 1, 6, 4
    };
    // this will store the devastation metric
    private static int[] _metric;

    // do the work
    private static void Main(string[] args)
    {
        int count = 0;

        while (_field.Sum() > 0)
        {
            Console.Out.WriteLine("Round {0}:", ++count);
            GetBlastPotential();
            int cell_to_bomb = FindBestBombingSite();
            PrintField(cell_to_bomb);
            Bomb(cell_to_bomb);
        }
        Console.Out.WriteLine("Done in {0} rounds", count);
    } 

    // convert 2D position to 1D index
    private static int Get1DCoord(int x, int y)
    {
        if ((x < 0) || (y < 0) || (x >= _width) || (y >= _length)) return -1;
        else
        {
            return (y * _width) + x;
        }
    }

    // Convert 1D index to 2D position
    private static void Get2DCoord(int n, out int x, out int y)
    {
        if ((n < 0) || (n >= _field.Length))
        {
            x = -1;
            y = -1;
        }
        else
        {
            x = n % _width;
            y = n / _width;
        }
    }

    // Compute a list of 1D indices for a cell neighbours
    private static List<int> GetNeighbours(int cell)
    {
        List<int> neighbours = new List<int>();
        int x, y;
        Get2DCoord(cell, out x, out y);
        if ((x >= 0) && (y >= 0))
        {
            List<int> tmp = new List<int>();
            tmp.Add(Get1DCoord(x - 1, y - 1));
            tmp.Add(Get1DCoord(x - 1, y));
            tmp.Add(Get1DCoord(x - 1, y + 1));
            tmp.Add(Get1DCoord(x, y - 1));
            tmp.Add(Get1DCoord(x, y + 1));
            tmp.Add(Get1DCoord(x + 1, y - 1));
            tmp.Add(Get1DCoord(x + 1, y));
            tmp.Add(Get1DCoord(x + 1, y + 1));

            // eliminate invalid coords - i.e. stuff past the edges
            foreach (int c in tmp) if (c >= 0) neighbours.Add(c);
        }
        return neighbours;
    }

    // Compute the devastation metric for each cell
    // Represent the Value of the cell minus the sum of all its neighbours
    private static void GetBlastPotential()
    {
        _metric = new int[_field.Length];
        for (int i = 0; i < _field.Length; i++)
        {
            _metric[i] = _field[i];
            List<int> neighbours = GetNeighbours(i);
            if (neighbours != null)
            {
                foreach (int j in neighbours) _metric[i] -= _field[j];
            }
        }
        for (int i = 0; i < _metric.Length; i++)
        {
            _metric[i] = (_metric[i] < 0) ? Math.Abs(_metric[i]) : 0;
        }
    }

    //// Compute the simple expected damage a bomb would score
    //private static void GetBlastPotential()
    //{
    //    _metric = new int[_field.Length];
    //    for (int i = 0; i < _field.Length; i++)
    //    {
    //        _metric[i] = (_field[i] > 0) ? 1 : 0;
    //        List<int> neighbours = GetNeighbours(i);
    //        if (neighbours != null)
    //        {
    //            foreach (int j in neighbours) _metric[i] += (_field[j] > 0) ? 1 : 0;
    //        }
    //    }            
    //}

    // Update the battle field upon dropping a bomb
    private static void Bomb(int cell)
    {
        List<int> neighbours = GetNeighbours(cell);
        foreach (int i in neighbours)
        {
            if (_field[i] > 0) _field[i]--;
        }
    }

    // Find the best bombing site - just return index of local maxima
    private static int FindBestBombingSite()
    {
        int max_idx = 0;
        int max_val = int.MinValue;
        for (int i = 0; i < _metric.Length; i++)
        {
            if (_metric[i] > max_val)
            {
                max_val = _metric[i];
                max_idx = i;
            }
        }
        return max_idx;
    }

    // Display the battle field on the console
    private static void PrintField(int cell)
    {
        for (int x = 0; x < _width; x++)
        {
            for (int y = 0; y < _length; y++)
            {
                int c = Get1DCoord(x, y);
                if (c == cell)
                    Console.Out.Write(string.Format("[{0}]", _field[c]).PadLeft(4));
                else
                    Console.Out.Write(string.Format(" {0} ", _field[c]).PadLeft(4));
            }
            Console.Out.Write(" || ");
            for (int y = 0; y < _length; y++)
            {
                int c = Get1DCoord(x, y);
                if (c == cell)
                    Console.Out.Write(string.Format("[{0}]", _metric[c]).PadLeft(4));
                else
                    Console.Out.Write(string.Format(" {0} ", _metric[c]).PadLeft(4));
            }
            Console.Out.WriteLine();
        }
        Console.Out.WriteLine();
    }           
  }
}

Il modello di bombardamento risultante viene emesso come segue (valori del campo a sinistra, metrica a destra)

Round 1:
  2   1   4   2   3   2   6  ||   7  16   8  10   4  18   6
  3   5   3   1   1   1   9  ||  11  18  18  21  17  28   5
  4  [2]  4   2   3   4   1  ||  19 [32] 21  20  17  24  22
  7   6   2   4   4   3   6  ||   8  17  20  14  16  22   8
  1   2   1   1   1   2   4  ||  14  15  14  11  13  16   7

Round 2:
  2   1   4   2   3   2   6  ||   5  13   6   9   4  18   6
  2   4   2   1   1  [1]  9  ||  10  15  17  19  17 [28]  5
  3   2   3   2   3   4   1  ||  16  24  18  17  17  24  22
  6   5   1   4   4   3   6  ||   7  14  19  12  16  22   8
  1   2   1   1   1   2   4  ||  12  12  12  10  13  16   7

Round 3:
  2   1   4   2   2   1   5  ||   5  13   6   7   3  15   5
  2   4   2   1   0   1   8  ||  10  15  17  16  14  20   2
  3  [2]  3   2   2   3   0  ||  16 [24] 18  15  16  21  21
  6   5   1   4   4   3   6  ||   7  14  19  11  14  19   6
  1   2   1   1   1   2   4  ||  12  12  12  10  13  16   7

Round 4:
  2   1   4   2   2   1   5  ||   3  10   4   6   3  15   5
  1   3   1   1   0   1   8  ||   9  12  16  14  14  20   2
  2   2   2   2   2  [3]  0  ||  13  16  15  12  16 [21] 21
  5   4   0   4   4   3   6  ||   6  11  18   9  14  19   6
  1   2   1   1   1   2   4  ||  10   9  10   9  13  16   7

Round 5:
  2   1   4   2   2   1   5  ||   3  10   4   6   2  13   3
  1   3   1   1   0  [0]  7  ||   9  12  16  13  12 [19]  2
  2   2   2   2   1   3   0  ||  13  16  15  10  14  15  17
  5   4   0   4   3   2   5  ||   6  11  18   7  13  17   6
  1   2   1   1   1   2   4  ||  10   9  10   8  11  13   5

Round 6:
  2   1   4   2   1   0   4  ||   3  10   4   5   2  11   2
  1   3   1   1   0   0   6  ||   9  12  16  11   8  13   0
  2   2   2   2   0   2   0  ||  13  16  15   9  14  14  15
  5   4  [0]  4   3   2   5  ||   6  11 [18]  6  11  15   5
  1   2   1   1   1   2   4  ||  10   9  10   8  11  13   5

Round 7:
  2   1   4   2   1   0   4  ||   3  10   4   5   2  11   2
  1   3   1   1   0   0   6  ||   8  10  13   9   7  13   0
  2  [1]  1   1   0   2   0  ||  11 [15] 12   8  12  14  15
  5   3   0   3   3   2   5  ||   3   8  10   3   8  15   5
  1   1   0   0   1   2   4  ||   8   8   7   7   9  13   5

Round 8:
  2   1   4   2   1   0   4  ||   1   7   2   4   2  11   2
  0   2   0   1   0   0   6  ||   7   7  12   7   7  13   0
  1   1   0   1   0   2   0  ||   8   8  10   6  12  14  15
  4   2   0   3   3  [2]  5  ||   2   6   8   2   8 [15]  5
  1   1   0   0   1   2   4  ||   6   6   6   7   9  13   5

Round 9:
  2   1   4   2   1   0   4  ||   1   7   2   4   2  11   2
  0   2   0   1   0   0   6  ||   7   7  12   7   6  12   0
  1   1   0   1   0  [1]  0  ||   8   8  10   5  10 [13] 13
  4   2   0   3   2   2   4  ||   2   6   8   0   6   9   3
  1   1   0   0   0   1   3  ||   6   6   6   5   8  10   4

Round 10:
  2   1   4   2   1   0   4  ||   1   7   2   4   2  10   1
  0   2  [0]  1   0   0   5  ||   7   7 [12]  7   6  11   0
  1   1   0   1   0   1   0  ||   8   8  10   4   8   9  10
  4   2   0   3   1   1   3  ||   2   6   8   0   6   8   3
  1   1   0   0   0   1   3  ||   6   6   6   4   6   7   2

Round 11:
  2   0   3   1   1   0   4  ||   0   6   0   3   0  10   1
  0   1   0   0   0  [0]  5  ||   4   5   5   5   3 [11]  0
  1   0   0   0   0   1   0  ||   6   8   6   4   6   9  10
  4   2   0   3   1   1   3  ||   1   5   6   0   5   8   3
  1   1   0   0   0   1   3  ||   6   6   6   4   6   7   2

Round 12:
  2   0   3   1   0   0   3  ||   0   6   0   2   1   7   1
  0   1   0   0   0   0   4  ||   4   5   5   4   1   7   0
  1   0   0   0   0  [0]  0  ||   6   8   6   4   5  [9]  8
  4   2   0   3   1   1   3  ||   1   5   6   0   4   7   2
  1   1   0   0   0   1   3  ||   6   6   6   4   6   7   2

Round 13:
  2   0   3   1   0   0   3  ||   0   6   0   2   1   6   0
  0   1   0   0   0   0   3  ||   4   5   5   4   1   6   0
  1  [0]  0   0   0   0   0  ||   6  [8]  6   3   3   5   5
  4   2   0   3   0   0   2  ||   1   5   6   0   4   6   2
  1   1   0   0   0   1   3  ||   6   6   6   3   4   4   0

Round 14:
  2   0   3   1   0  [0]  3  ||   0   5   0   2   1  [6]  0
  0   0   0   0   0   0   3  ||   2   5   4   4   1   6   0
  0   0   0   0   0   0   0  ||   4   4   4   3   3   5   5
  3   1   0   3   0   0   2  ||   0   4   5   0   4   6   2
  1   1   0   0   0   1   3  ||   4   4   5   3   4   4   0

Round 15:
  2   0   3   1   0   0   2  ||   0   5   0   2   1   4   0
  0   0   0   0   0   0   2  ||   2   5   4   4   1   4   0
  0   0   0   0   0   0   0  ||   4   4   4   3   3   4   4
  3   1   0   3   0  [0]  2  ||   0   4   5   0   4  [6]  2
  1   1   0   0   0   1   3  ||   4   4   5   3   4   4   0

Round 16:
  2  [0]  3   1   0   0   2  ||   0  [5]  0   2   1   4   0
  0   0   0   0   0   0   2  ||   2   5   4   4   1   4   0
  0   0   0   0   0   0   0  ||   4   4   4   3   3   3   3
  3   1   0   3   0   0   1  ||   0   4   5   0   3   3   1
  1   1   0   0   0   0   2  ||   4   4   5   3   3   3   0

Round 17:
  1   0   2   1   0   0   2  ||   0   3   0   1   1   4   0
  0   0   0   0   0   0   2  ||   1   3   3   3   1   4   0
  0   0   0   0   0   0   0  ||   4   4   4   3   3   3   3
  3   1  [0]  3   0   0   1  ||   0   4  [5]  0   3   3   1
  1   1   0   0   0   0   2  ||   4   4   5   3   3   3   0

Round 18:
  1   0   2   1   0   0   2  ||   0   3   0   1   1   4   0
  0   0   0   0   0   0   2  ||   1   3   3   3   1   4   0
  0   0   0   0   0   0   0  ||   3   3   2   2   2   3   3
  3  [0]  0   2   0   0   1  ||   0  [4]  2   0   2   3   1
  1   0   0   0   0   0   2  ||   2   4   2   2   2   3   0

Round 19:
  1   0   2   1   0  [0]  2  ||   0   3   0   1   1  [4]  0
  0   0   0   0   0   0   2  ||   1   3   3   3   1   4   0
  0   0   0   0   0   0   0  ||   2   2   2   2   2   3   3
  2   0   0   2   0   0   1  ||   0   2   2   0   2   3   1
  0   0   0   0   0   0   2  ||   2   2   2   2   2   3   0

Round 20:
  1  [0]  2   1   0   0   1  ||   0  [3]  0   1   1   2   0
  0   0   0   0   0   0   1  ||   1   3   3   3   1   2   0
  0   0   0   0   0   0   0  ||   2   2   2   2   2   2   2
  2   0   0   2   0   0   1  ||   0   2   2   0   2   3   1
  0   0   0   0   0   0   2  ||   2   2   2   2   2   3   0

Round 21:
  0   0   1   1   0   0   1  ||   0   1   0   0   1   2   0
  0   0   0   0   0   0   1  ||   0   1   2   2   1   2   0
  0   0   0   0   0   0   0  ||   2   2   2   2   2   2   2
  2   0   0   2   0  [0]  1  ||   0   2   2   0   2  [3]  1
  0   0   0   0   0   0   2  ||   2   2   2   2   2   3   0

Round 22:
  0   0   1   1   0   0   1  ||   0   1   0   0   1   2   0
  0   0   0   0   0   0   1  ||   0   1   2   2   1   2   0
 [0]  0   0   0   0   0   0  ||  [2]  2   2   2   2   1   1
  2   0   0   2   0   0   0  ||   0   2   2   0   2   1   1
  0   0   0   0   0   0   1  ||   2   2   2   2   2   1   0

Round 23:
  0   0   1   1   0   0   1  ||   0   1   0   0   1   2   0
  0   0  [0]  0   0   0   1  ||   0   1  [2]  2   1   2   0
  0   0   0   0   0   0   0  ||   1   1   2   2   2   1   1
  1   0   0   2   0   0   0  ||   0   1   2   0   2   1   1
  0   0   0   0   0   0   1  ||   1   1   2   2   2   1   0

Round 24:
  0   0   0   0   0   0   1  ||   0   0   0   0   0   2   0
  0   0   0   0   0   0   1  ||   0   0   0   0   0   2   0
  0   0  [0]  0   0   0   0  ||   1   1  [2]  2   2   1   1
  1   0   0   2   0   0   0  ||   0   1   2   0   2   1   1
  0   0   0   0   0   0   1  ||   1   1   2   2   2   1   0

Round 25:
  0   0   0   0   0  [0]  1  ||   0   0   0   0   0  [2]  0
  0   0   0   0   0   0   1  ||   0   0   0   0   0   2   0
  0   0   0   0   0   0   0  ||   1   1   1   1   1   1   1
  1   0   0   1   0   0   0  ||   0   1   1   0   1   1   1
  0   0   0   0   0   0   1  ||   1   1   1   1   1   1   0

Round 26:
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
 [0]  0   0   0   0   0   0  ||  [1]  1   1   1   1   0   0
  1   0   0   1   0   0   0  ||   0   1   1   0   1   1   1
  0   0   0   0   0   0   1  ||   1   1   1   1   1   1   0

Round 27:
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0  [0]  0   0   0   0  ||   0   0  [1]  1   1   0   0
  0   0   0   1   0   0   0  ||   0   0   1   0   1   1   1
  0   0   0   0   0   0   1  ||   0   0   1   1   1   1   0

Round 28:
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0   0   0   0  [0]  0  ||   0   0   0   0   0  [1]  1
  0   0   0   0   0   0   1  ||   0   0   0   0   0   1   0

Done in 28 rounds

1

Questo può essere risolto usando un albero di profondità O (3 ^ (n)). Dove n è la somma di tutti i quadrati.

Per prima cosa considera che è banale risolvere il problema con un albero di O (9 ^ n), considera semplicemente tutte le possibili posizioni dei bombardamenti. Per un esempio vedere l'implementazione di Alfe .

Quindi renditi conto che possiamo lavorare per bombardare dal basso verso l'alto e ottenere comunque un modello di bombardamento minimo.

  1. Inizia dall'angolo in basso a sinistra.
  2. Bombardalo fino all'oblio con le uniche giocate che hanno senso (in alto ea destra).
  3. Spostati di un quadrato a destra.
  4. Anche se l'obiettivo ha un valore maggiore di zero, considera ciascuna delle 2 giocate che hanno senso (verso l'alto o verso l'alto e verso destra), riduci il valore dell'obiettivo di uno e crea un nuovo ramo per ogni possibilità.
  5. Sposta un altro a destra.
  6. Anche se l'obiettivo ha un valore maggiore di zero, considera ciascuna delle 3 giocate che hanno senso (in alto a sinistra, in alto e in alto a destra), riduci il valore dell'obiettivo di uno e crea un nuovo ramo per ogni possibilità.
  7. Ripeti i passaggi 5 e 6 fino a quando la riga non viene eliminata.
  8. Salire di una riga e ripetere i passaggi da 1 a 7 finché il puzzle non è risolto.

Questo algoritmo è corretto perché

  1. È necessario completare ogni riga ad un certo punto.
  2. Il completamento di una riga richiede sempre una giocata sopra, sotto o all'interno di quella riga.
  3. È sempre buono o migliore scegliere un gioco sopra la riga più bassa non cancellata rispetto a un gioco sulla riga o sotto la riga.

In pratica questo algoritmo funzionerà regolarmente meglio del suo massimo teorico perché bombarderà regolarmente i vicini e ridurrà le dimensioni della ricerca. Se assumiamo che ogni bombardamento diminuisca il valore di 4 bersagli aggiuntivi, il nostro algoritmo verrà eseguito in O (3 ^ (n / 4)) o approssimativamente O (1.3 ^ n).

Poiché questo algoritmo è ancora esponenziale, sarebbe saggio limitare la profondità della ricerca. Potremmo limitare il numero di rami consentiti a un certo numero, X, e una volta che siamo così in profondità costringiamo l'algoritmo a scegliere il percorso migliore che ha identificato finora (quello che ha la somma minima totale della scheda in una delle sue foglie terminali ). Quindi il nostro algoritmo è garantito per funzionare in tempo O (3 ^ X), ma non è garantito che ottenga la risposta corretta. Tuttavia, possiamo sempre aumentare X e testare empiricamente se vale la pena fare un compromesso tra una maggiore computazione e migliori risposte.


1

funzione di valutazione, somma totale:

int f (int ** matrix, int width, int height, int x, int y)
{
    int m[3][3] = { 0 };

    m[1][1] = matrix[x][y];
    if (x > 0) m[0][1] = matrix[x-1][y];
    if (x < width-1) m[2][1] = matrix[x+1][y];

    if (y > 0)
    {
        m[1][0] = matrix[x][y-1];
        if (x > 0) m[0][0] = matrix[x-1][y-1];
        if (x < width-1) m[2][0] = matrix[x+1][y-1];
    }

    if (y < height-1)
    {
        m[1][2] = matrix[x][y+1];
        if (x > 0) m[0][2] = matrix[x-1][y+1];
        if (x < width-1) m[2][2] = matrix[x+1][y+1];
    }

    return m[0][0]+m[0][1]+m[0][2]+m[1][0]+m[1][1]+m[1][2]+m[2][0]+m[2][1]+m[2][2];
}

funzione obiettivo:

Point bestState (int ** matrix, int width, int height)
{
    Point p = new Point(0,0);
    int bestScore = 0;
    int b = 0;

    for (int i=0; i<width; i++)
        for (int j=0; j<height; j++)
        {
            b = f(matrix,width,height,i,j);

            if (b > bestScore)
            {
                bestScore = best;
                p = new Point(i,j);
            }
        }

    retunr p;
}

distruggere la funzione:

void destroy (int ** matrix, int width, int height, Point p)
{
    int x = p.x;
    int y = p.y;

    if(matrix[x][y] > 0) matrix[x][y]--;
    if (x > 0) if(matrix[x-1][y] > 0) matrix[x-1][y]--;
    if (x < width-1) if(matrix[x+1][y] > 0) matrix[x+1][y]--;

    if (y > 0)
    {
        if(matrix[x][y-1] > 0) matrix[x][y-1]--;
        if (x > 0) if(matrix[x-1][y-1] > 0) matrix[x-1][y-1]--;
        if (x < width-1) if(matrix[x+1][y-1] > 0) matrix[x+1][y-1]--;
    }

    if (y < height-1)
    {
        if(matrix[x][y] > 0) matrix[x][y+1]--;
        if (x > 0) if(matrix[x-1][y+1] > 0) matrix[x-1][y+1]--;
        if (x < width-1) if(matrix[x+1][y+1] > 0) matrix[x+1][y+1]--;
    }
}

funzione obiettivo:

bool isGoal (int ** matrix, int width, int height)
{
    for (int i=0; i<width; i++)
        for (int j=0; j<height; j++)
            if (matrix[i][j] > 0)
                return false;
    return true;
}

funzione di massimizzazione lineare:

void solve (int ** matrix, int width, int height)
{
    while (!isGoal(matrix,width,height))
    {
        destroy(matrix,width,height, bestState(matrix,width,height));
    }
}

Questo non è ottimale, ma può essere ottimizzato trovando una migliore funzione di valutazione.

.. ma pensando a questo problema, stavo pensando che uno dei problemi principali è ottenere cifre abbandonate nel mezzo degli zeri ad un certo punto, quindi prenderei un altro approccio .. che è dominare i valori minimi in zero, quindi provare a sfuggire agli zeri possibile, il che porta alla minimizzazione generale del valore minimo esistente o giù di lì


0

Tutto questo problema si riduce a calcolare una distanza di modifica. Calcola semplicemente una variante della distanza di Levenshtein tra la matrice data e la matrice zero, dove le modifiche vengono sostituite con i bombardamenti, utilizzando la programmazione dinamica per memorizzare le distanze tra gli array intermedi. Suggerisco di utilizzare un hash delle matrici come chiave. In pseudo-Python:

memo = {}

def bomb(matrix,i,j):
    # bomb matrix at i,j

def bombsRequired(matrix,i,j):
    # bombs required to zero matrix[i,j]

def distance(m1, i, len1, m2, j, len2):
    key = hash(m1)
    if memo[key] != None: 
        return memo[key]

    if len1 == 0: return len2
    if len2 == 0: return len1

    cost = 0
    if m1 != m2: cost = m1[i,j]
    m = bomb(m1,i,j)
    dist = distance(str1,i+1,len1-1,str2,j+1,len2-1)+cost)
    memo[key] = dist
    return dist

0

Questa era una risposta alla prima domanda posta. Non avevo notato che ha cambiato i parametri.

Crea un elenco di tutti i target. Assegna un valore all'obiettivo in base al numero di valori positivi influenzati da una caduta (se stesso e tutti i vicini). Il valore più alto sarebbe un nove.

Ordina i target in base al numero di target interessati (Decrescente), con un ordinamento discendente secondario sulla somma di ciascun target interessato.

Sgancia una bomba sul bersaglio con il punteggio più alto, quindi ricalcola i bersagli e ripeti finché tutti i valori dei bersagli non sono zero.

D'accordo, questo non è sempre il più ottimale. Per esempio,

100011
011100
011100
011100
000000
100011

Questo approccio richiederebbe 5 bombe per eliminare. In modo ottimale, però, potresti farlo in 4. Tuttavia, è abbastanza vicino e non c'è modo di tornare indietro. Per la maggior parte delle situazioni sarà ottimale o molto vicino.

Utilizzando i numeri del problema originali, questo approccio risolve in 28 bombe.

Aggiunta di codice per dimostrare questo approccio (utilizzando un modulo con un pulsante):

         private void button1_Click(object sender, EventArgs e)
    {
        int[,] matrix = new int[10, 10] {{5, 20, 7, 1, 9, 8, 19, 16, 11, 3}, 
                                         {17, 8, 15, 17, 12, 4, 5, 16, 8, 18},
                                         { 4, 19, 12, 11, 9, 7, 4, 15, 14, 6},
                                         { 17, 20, 4, 9, 19, 8, 17, 2, 10, 8},
                                         { 3, 9, 10, 13, 8, 9, 12, 12, 6, 18}, 
                                         {16, 16, 2, 10, 7, 12, 17, 11, 4, 15},
                                         { 11, 1, 15, 1, 5, 11, 3, 12, 8, 3},
                                         { 7, 11, 16, 19, 17, 11, 20, 2, 5, 19},
                                         { 5, 18, 2, 17, 7, 14, 19, 11, 1, 6},
                                         { 13, 20, 8, 4, 15, 10, 19, 5, 11, 12}};


        int value = 0;
        List<Target> Targets = GetTargets(matrix);
        while (Targets.Count > 0)
        {
            BombTarget(ref matrix, Targets[0]);
            value += 1;
            Targets = GetTargets(matrix);
        }
        Console.WriteLine( value);
        MessageBox.Show("done: " + value);
    }

    private static void BombTarget(ref int[,] matrix, Target t)
    {
        for (int a = t.x - 1; a <= t.x + 1; a++)
        {
            for (int b = t.y - 1; b <= t.y + 1; b++)
            {
                if (a >= 0 && a <= matrix.GetUpperBound(0))
                {
                    if (b >= 0 && b <= matrix.GetUpperBound(1))
                    {
                        if (matrix[a, b] > 0)
                        {
                            matrix[a, b] -= 1;
                        }
                    }
                }
            }
        }
        Console.WriteLine("Dropped bomb on " + t.x + "," + t.y);
    }

    private static List<Target> GetTargets(int[,] matrix)
    {
        List<Target> Targets = new List<Target>();
        int width = matrix.GetUpperBound(0);
        int height = matrix.GetUpperBound(1);
        for (int x = 0; x <= width; x++)
        {
            for (int y = 0; y <= height; y++)
            {
                Target t = new Target();
                t.x = x;
                t.y = y;
                SetTargetValue(matrix, ref t);
                if (t.value > 0) Targets.Add(t);
            }
        }
        Targets = Targets.OrderByDescending(x => x.value).ThenByDescending( x => x.sum).ToList();
        return Targets;
    }

    private static void SetTargetValue(int[,] matrix, ref Target t)
    {
        for (int a = t.x - 1; a <= t.x + 1; a++)
        {
            for (int b = t.y - 1; b <= t.y + 1; b++)
            {
                if (a >= 0 && a <= matrix.GetUpperBound(0))
                {
                    if (b >= 0 && b <= matrix.GetUpperBound(1))
                    {
                        if (matrix[ a, b] > 0)
                        {
                            t.value += 1;
                            t.sum += matrix[a,b];
                        }

                    }
                }
            }
        }

    }

Una classe di cui avrai bisogno:

        class Target
    {
        public int value;
        public int sum;
        public int x;
        public int y;
    }

1
Non ottimale. Controesempio: 09090questo approccio richiede 18 bombe. Può essere fatto in 9.
Mysticial

@Mysticial Non hai letto a fondo la risposta. Poiché si basa sul numero di campi diversi da zero colpiti, questo algoritmo bombarderebbe lo zero centrale e verrebbe eseguito in 9 cadute. Il valore elevato di 9 è dovuto al fatto che ci sono fino a otto vicini e se stesso.
Anthony Queen

Poi come su 1010101, 0010100?
Mysticial

@Mysticial Per il primo, il primo zero, poi l'ultimo zero verrebbero colpiti. Sarebbero due gocce. Questo perché ogni volta che sgancia una bomba, ricalcola il miglior bersaglio successivo. Per l'ultimo esempio, di nuovo lo zero centrale; Una goccia.
Anthony Queen

1
@AnthonyQueen: questo non funziona. per favore vedi chat.stackoverflow.com/transcript/message/8224273#8224273 per il mio controesempio.
nneonneo
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.