Esiste una soluzione più rapida per il problema della Grande Muraglia di Google Code Jam


16

Considera la seguente domanda di Google Code Jam round 1C :

La Grande Muraglia cinese inizia come una linea infinita, dove l'altezza in tutte le posizioni è .0

Alcuni numero di tribù , N 1000 , attaccherà la parete del muro in base ai seguenti parametri - un giorno di inizio, D , un punto di forza di partenza S , una start-occidentale di coordinate, W , e un inizio est coordinata, E . Questo primo attacco avviene il giorno D , sulla gamma [ W , E ] , in forza S . Se c'è una parte della Grande Muraglia all'interno di [ W , l'attacco ha successo, e alla fine della giornata, il muro sarà costruito in modo tale che qualsiasi segmento di esso all'interno di [ W , ENN1000DSWED[W,E]S che ha altezza < S[W,E]<S di altezza < S sarebbe quindi all'altezza S (o maggiore, se qualche altro attacco quel giorno colpì sullo stesso segmento con forza S > S )[W,E]<SSS>S

Ogni tribù eseguirà fino a attacchi prima di ritirarsi e ogni attacco sarà determinato iterativamente da quello precedente. Ogni tribù ha alcuni δ D , δ X e δ S che determinano la loro sequenza di attacchi: aspetteranno δ D1000δDδXδS giorni tra gli attacchi, sposteranno il loro raggio di attacco δ unità X per ogni attacco (negativo = ovest, positivo = est), sebbene le dimensioni dell'intervallo rimangano invariate e anche la loro forza aumenterà / diminuirà di un valore costante dopo ogni attacco.δD1δX

L'obiettivo del problema è, data una descrizione completa delle tribù attaccanti, determinare quanti dei loro attacchi avranno successo.

Sono riuscito a codificare una soluzione che funziona, in esecuzione in circa 20 secondi: credo che la soluzione che ho implementato impieghi il tempo , dove A = il numero totale di attacchi in una simulazione (max 1000000 ) e X = il numero totale di punti limite univoci nelle gamme di attacco (max 2000000 ).O(AlogA+(A+X)logX)A=1000000X=2000000

Ad alto livello, la mia soluzione:

  • Legge tutte le informazioni sulla tribù
  • Calcola tutte le coordinate uniche per i range di attacco - O ( A )XO(A)
  • Rappresenta il muro come un albero binario aggiornato pigramente sugli intervalli che tiene traccia dei valori di altezza minima. Una foglia è l'arco di due coordinate X con nulla in mezzo e tutti i nodi padre rappresentano l'intervallo continuo coperto dai loro figli. - O ( registro XXXO(XlogX)
  • Genera tutti gli attacchi che ogni tribù eseguirà e li ordina per giorno - O(AlogA)
  • Per ogni attacco, verifica se ha esito positivo ( tempo di query ). Quando il giorno cambia, esegui il ciclo tra tutti gli attacchi riusciti non elaborati e aggiorna il muro di conseguenza ( registra il tempo di aggiornamento X per ogni attacco). - O ( un registrologXlogXO(AlogX)

La mia domanda è questa: esiste un modo per fare meglio di ? Forse esiste un modo strategico per trarre vantaggio dalla natura lineare degli attacchi successivi delle Tribù? 20 secondi sembra troppo lungo per una soluzione prevista (anche se Java potrebbe essere la colpa per quello).O(AlogA+(A+X)logX)


3
Per favore, non chiuderlo. È una domanda valida Una risposta sarebbe una prova con limite inferiore, dimostrando che non possiamo fare di meglio, se è davvero il meglio che possiamo fare. Ad esempio, immagino che potremmo essere in grado di utilizzare il problema di distinzione degli elementi qui, ma non ho trovato il tempo di pensarci.
Aryabhata,

Lo terrò quindi aperto :)
torquestomp

Risposte:


2

L'ovvio margine di miglioramento è questo passaggio:

Genera tutti gli attacchi che ogni tribù eseguirà e li ordina di giorno - O(AlogA)

Sappiamo che le tribù attaccheranno da un giorno particolare, a intervalli regolari. Ciò significa che dovremmo fondere essenzialmente un sacco di elenchi preordinati. Anche la dichiarazione del problema ci dice che non ci saranno mai più di 1.000 tribù (cioè 1.000 liste da unire); un numero esiguo rispetto ai 1.000.000 di attacchi massimi! A seconda dei tempi relativi all'implementazione, il passaggio a questa opzione potrebbe dimezzare i tempi di elaborazione.

Questo è davvero tutto ciò che posso suggerire per ottimizzare la complessità teorica, ma non ho prove che sarebbe ottimale dopo questo cambiamento.


Ho provato il puzzle da solo, ma ho usato una rappresentazione molto più stupida del muro: un albero di ricerca binario ( std::mapper la precisione C ++ ) che memorizza le posizioni in cui l'altezza del muro cambia. Con questo, sono stato in grado di aggiungere e rimuovere nodi come richiesto (cioè se una sezione complicata fosse soggetta a un attacco ampio e travolgente o più attacchi con la stessa forza toccata, il numero di nodi si ridurrebbe in modo significativo). Ciò ha risolto l'input di grandi dimensioni in 3,9 secondi (sul mio laptop di sviluppo di fascia media). Ho il sospetto che ci siano diverse ragioni per il miglioramento:

  • Come hai sottolineato, boxe e unboxing possono diventare costosi, ma i contenitori basati su template di C ++ lo evitano del tutto.
  • Mentre la rappresentazione del muro che ho usato è teoricamente peggiore, nella stragrande maggioranza dei casi la capacità di ridurre dinamicamente il numero di nodi lo ha reso superveloce (la maggior parte dei casi di test ha raggiunto il limite massimo sotto i nodi 1k e tutti tranne 2 erano sotto i 10k) . In effetti, l'unico caso che ha richiesto un tempo significativo è stato il n. 7, che sembra aver testato molti intervalli non intersecanti.
  • Non ho usato nessuna pre-elaborazione (le fasi sono determinate tenendo traccia di quando ogni tribù attaccherà successivamente e cercando il più basso comune in ogni turno). Ancora una volta questo è teoricamente peggio, ma per la maggior parte dei casi sospetto che il sovraccarico inferiore significhi che è stato più veloce (lo testerò e ti risponderò). Aggiornamento : ho aggiunto una coda di priorità per gli attacchi, simile al metodo sopra descritto (anche se invece di creare l'array di grandi dimensioni l'ho calcolato al volo) e ho visto il tempo diminuire a 3,0 secondi per l'input di grandi dimensioni.

In breve, mentre penso che il tuo algoritmo sia quasi ottimale nel caso generale, ci sono diversi modi in cui potresti accelerarlo per input tipici .


1

Quanto segue è stato rimosso dalla domanda, in quanto si tratta di una risposta.

Osservare altre discussioni e soluzioni di successo sembra indicare che la soluzione che ho descritto è praticamente l'algoritmo previsto. Il rallentamento nella mia soluzione è probabilmente dovuto solo all'uso pigro del boxing automatico e di una struttura ad albero basata su puntatore, piuttosto che a quella basata su array - quindi sospetto che, se esiste una soluzione, probabilmente non è un tutto molto meglio di quello che è qui.

La soluzione può essere trovata qui . È molto simile a quello che ho pubblicato qui; quindi sono molto più propenso a credere che non esista una soluzione più efficiente.

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.