Java ( meno grottesco: 8415 5291 3301)
Ok. Fondamentalmente, sono imbarazzato che nessuno abbia presentato una soluzione. Quindi qualche giorno fa ho iniziato a provare a risolvere questo problema, b / c è fantastico. . Segui quel link per vedere i miei progressi su di esso tramite GitHub.
modificare
Nuova versione del solutore, molto più "giocata a golf", con correttore di cicli corretto identificato da MT0. Supporta inoltre percorsi di avanzamento rapido, regolabili modificando la quantità di memoria disponibile per la VM. Ultima modifica GRANDE : mi sono reso conto che avevo alcuni altri piccoli errori nell'indice e ottimizzazioni premature, che hanno provocato l'incapacità di considerare un numero piuttosto elevato di tipi di vincite. Quindi è stato risolto, con attenzione. La nuova versione è sia più piccola che inferiore. Per il nostro percorso di riferimento, java -Xmx2GB ZombieHordeMinfa il trucco abbastanza bene (attenzione, ci vorrà del tempo).
Factoide freddo
In una svolta affascinante, ci sono MOLTE soluzioni alla lunghezza 24, e il mio risolutore trova una diversa da quella di MT0, ma identica in linea di principio, tranne per il fatto che inizia visitando gli altri avamposti collegati 1. Affascinante! Totalmente contro l'intuizione umana, ma perfettamente valida.
Aspetti salienti della soluzione
Quindi ecco il mio. È (parzialmente) golfato, b / c è un risolutore esponenziale, quasi a forza bruta. Uso un algoritmo IDDFS (prima ricerca approfondita della profondità di approfondimento), quindi è un ottimo solutore generale che non salta, quindi risolve entrambe le parti della domanda del PO, vale a dire:
- Se viene trovata una rotta vincente (infiniti zombi), genera 'x'.
- Se tutte le rotte finiscono con la morte (zombi finiti), genera il maggior numero di zombi uccisi.
Dagli abbastanza potenza, memoria e tempo, e farà proprio questo, anche le mappe a morte lenta. Ho trascorso un po 'più di tempo a migliorare questo risolutore, e mentre si può fare di più, ora è un po' meglio. Ho anche integrato i consigli di MT0 sulla migliore soluzione di zombi infiniti e ho rimosso diverse ottimizzazioni premature dal mio win-checker che ha impedito alla versione precedente di trovarla, e ora in effetti trovo una soluzione molto simile a quella di MT0 descritta.
Alcuni altri punti salienti:
- Come accennato, utilizza un IDDFS per trovare il percorso vincente più breve possibile.
- Dal momento che è al centro di un DFS, scoprirà anche se ogni rotta termina con la morte del nostro eroe e tiene traccia della rotta "migliore" in termini di maggior parte degli zombi uccisi. Muori un eroe!
Ho strumentato l'algoritmo per rendere più interessante la visione di Removed per scopi di golf. Segui uno dei link a github per vedere la versione non giocata.
Ci sono anche molti commenti, quindi sentiti libero di ri-implementare la tua soluzione sulla base del mio approccio o mostrami come dovrebbe essere fatto!
- Avanzamento rapido del percorso adattivo alla memoria
- Fino alla memoria di sistema disponibile, terrà traccia delle "rotte finali" che non hanno provocato la morte.
- Utilizzando una routine di compressione e decompressione del percorso elaborata, i progressi da una precedente iterazione di IDDFS vengono ripristinati per impedire la riscoperta di tutti i percorsi visitati precedenti.
- Come bonus laterale intenzionale, funge da abbattimento di un vicolo cieco. Le rotte dei vicoli ciechi non vengono memorizzate e non verranno mai più visitate nelle profondità future di IDDFS.
Storia del risolutore
- Ho provato un sacco di algoritmi look-ahead in un solo passaggio, e mentre per scenari molto semplici avrebbero funzionato, alla fine sono rimasti piatti.
- Poi ho provato un algoritmo look-ahead in due passaggi, che era ... insoddisfacente.
- Ho quindi iniziato a costruire un look -head n-step, quando ho riconosciuto che questo approccio è riducibile a DFS, ma DFS è molto ... più elegante.
- Durante la costruzione del DFS, mi è venuto in mente che IDDFS avrebbe assicurato (a) di trovare la migliore rotta HERO (morte) o (b) il primo ciclo vincente.
- Scoprire che costruire una pedina del ciclo di vittoria è facile, ma ho dovuto passare diverse iterazioni molto sbagliate prima di arrivare a una pedina di successo.
- Factored nel percorso di vittoria di MT0 per rimuovere tre linee di ottimizzazione prematura che hanno reso cieco il mio algoritmo.
- Aggiunto un algoritmo adattivo di memorizzazione nella cache del percorso che utilizzerà tutta la memoria che gli viene fornita per evitare inutili ripetizioni del lavoro tra le chiamate IDDFS e elimina i percorsi senza uscita fino ai limiti della memoria.
Il codice (golf)
Passa al codice (scarica la versione non controllata qui o qui ):
import java.util.*;public class ZombieHordeMin{int a=100,b,m,n,i,j,z,y,D=0,R,Z,N;int p[][][];Scanner in;Runtime rt;int[][]r;int pp;int dd;int[][]bdr;int ww;int[][]bwr;int[][]faf;int ff;boolean ffOn;public static void main(String[]a){(new ZombieHordeMin()).pR();}ZombieHordeMin(){in=new Scanner(System.in);rt=Runtime.getRuntime();m=in.nextInt();N=in.nextInt();p=new int[m+1][m+1][N+1];int[]o=new int[m+1];for(b=0;b<N;b++){i=in.nextInt();j=in.nextInt();z=in.nextInt();o[i]++;o[j]++;D=(o[i]>D?o[i]:D);p[i][j][++p[i][j][0]]=z;if(i!=j)p[j][i][++p[j][i][0]]=z;D=(o[j]>D?o[j]:D);}m++;}void pR(){r=new int[5000][m+3];r[0][0]=a;Arrays.fill(r[0],1,m,1);r[0][m]=1;r[0][m+1]=0;r[0][m+2]=0;ww=-1;pp=dd=0;pR(5000);}void pR(int aMD){faf=new int[D][];ff=0;ffOn=true;for(int mD=1;mD<=aMD;mD++){System.out.printf("Checking len %d\n",mD);int k=ffR(0,mD);if(ww>-1){System.out.printf("%d x\n",ww+1);for(int win=0;win<=ww;win++)System.out.printf(" %d:%d,%d-%d",win,bwr[win][0],bwr[win][1],bwr[win][2]);System.out.println();break;}if(k>0){System.out.printf("dead max %d kills, %d steps\n",pp,dd+1);for(int die=0;die<=dd;die++)System.out.printf(" %d:%d,%d-%d",die,bdr[die][0],bdr[die][1],bdr[die][2]);System.out.println();break;}}}int ffR(int dP,int mD){if(ff==0)return pR(dP,mD);int kk=0;int fm=ff;if(ffOn&&D*fm>rt.maxMemory()/(faf[0][0]*8+12))ffOn=false;int[][]fmv=faf;if(ffOn){faf=new int[D*fm][];ff=0;}for(int df=0;df<fm;df++){dS(fmv[df]);kk+=pR(fmv[df][0],mD);}fmv=null;rt.gc();return kk==fm?1:0;}int pR(int dP,int mD){if(dP==mD)return 0;int rT=0;int dC=0;int src=r[dP][m];int sa=r[dP][0];for(int dt=1;dt<m;dt++){for(int rut=1;rut<=p[src][dt][0];rut++){rT++;r[dP+1][0]=sa-p[src][dt][rut]+r[dP][dt];for(int cp=1;cp<m;cp++)r[dP+1][cp]=(dt==cp?1:r[dP][cp]+1);r[dP+1][m]=dt;r[dP+1][m+1]=rut;r[dP+1][m+2]=r[dP][m+2]+p[src][dt][rut];if(sa-p[src][dt][rut]<1){dC++;if(pp<r[dP][m+2]+sa){pp=r[dP][m+2]+sa;dd=dP+1;bdr=new int[dP+2][3];for(int cp=0;cp<=dP+1;cp++){bdr[cp][0]=r[cp][m];bdr[cp][1]=r[cp][m+1];bdr[cp][2]=r[cp][0];}}}else{for(int chk=0;chk<=dP;chk++){if(r[chk][m]==dt){int fR=chk+1;for(int cM=0;cM<m+3;cM++)r[dP+2][cM]=r[dP+1][cM];for(;fR<=dP+1;fR++){r[dP+2][0]=r[dP+2][0]-p[r[dP+2][m]][r[fR][m]][r[fR][m+1]]+r[dP+2][r[fR][m]];for(int cp=1;cp<m;cp++)r[dP+2][cp]=(r[fR][m]==cp?1:r[dP+2][cp]+1);r[dP+2][m+2]=r[dP+2][m+2]+p[r[dP+2][m]][r[fR][m]][r[fR][m+1]];r[dP+2][m]=r[fR][m];r[dP+2][m+1]=r[fR][m+1];}if(fR==dP+2&&r[dP+2][0]>=r[dP+1][0]){ww=dP+1;bwr=new int[dP+2][3];for(int cp=0;cp<dP+2;cp++){bwr[cp][0]=r[cp][m];bwr[cp][1]=r[cp][m+1];bwr[cp][2]=r[cp][0];}return 0;}}}dC+=pR(dP+1,mD);if(ww>-1)return 0;}for(int cp=0;cp<m+3;cp++)r[dP+1][cp]=0;}}if(rT==dC)return 1;else{if(ffOn&&dP==mD-1)faf[ff++]=cP(dP);return 0;}}int[]cP(int dP){int[]cmp=new int[dP*2+3];cmp[0]=dP;cmp[dP*2+1]=r[dP][0];cmp[dP*2+2]=r[dP][m+2];for(int zip=1;zip<=dP;zip++){cmp[zip]=r[zip][m];cmp[dP+zip]=r[zip][m+1];}return cmp;}void dS(int[]cmp){int[]lv=new int[m];int dP=cmp[0];r[dP][0]=cmp[dP*2+1];r[dP][m+2]=cmp[dP*2+2];r[0][0]=100;r[0][m]=1;for(int dp=1;dp<=dP;dp++){r[dp][m]=cmp[dp];r[dp][m+1]=cmp[dP+dp];r[dp-1][cmp[dp]]=dp-lv[cmp[dp]];r[dp][m+2]=r[dp-1][m+2]+p[r[dp-1][m]][cmp[dp]][cmp[dP+dp]];r[dp][0]=r[dp-1][0]+r[dp-1][cmp[dp]]-p[r[dp-1][m]][cmp[dp]][cmp[dP+dp]];lv[cmp[dp]]=dp;}for(int am=1;am<m;am++)r[dP][am]=(am==cmp[dP]?1:dP-lv[am]+1);}}
Ottieni il codice da github qui, per tenere traccia di tutte le modifiche che apporto. Ecco alcune altre mappe che ho usato.
Esempio di output
Esempio di output per soluzione di riferimento:
$ java -d64 -Xmx3G ZombieHordeMin > reference_route_corrected_min.out
5 6 1 2 4 2 3 4 3 1 4 2 4 10 2 5 10 1 1 50
Checking len 1
Checking len 2
Checking len 3
Checking len 4
Checking len 5
Checking len 6
Checking len 7
Checking len 8
Checking len 9
Checking len 10
Checking len 11
Checking len 12
Checking len 13
Checking len 14
Checking len 15
Checking len 16
Checking len 17
Checking len 18
Checking len 19
Checking len 20
Checking len 21
Checking len 22
Checking len 23
Checking len 24
25 x
0:1,0-100 1:3,1-97 2:1,1-95 3:2,1-94 4:5,1-88 5:2,1-80 6:4,1-76 7:2,1-68 8:1,1-70 9:2,1-68 10:1,1-66 11:2,1-64 12:1,1-62 13:2,1-60 14:1,1-58 15:2,1-56 16:1,1-54 17:2,1-52 18:1,1-50 19:2,1-48 20:1,1-46 21:2,1-44 22:1,1-42 23:2,1-40 24:1,1-38
Leggi l'output del percorso in questo modo step:: source, route-to-get-here- ammo. Quindi nella soluzione sopra, lo leggeresti come:
- Al passo
0, all'avamposto 1con munizioni 100.
- Al passaggio
1, usa il percorso 1per arrivare all'avamposto 3con munizioni finali97
- Al passaggio
2, usa il percorso 1per arrivare all'avamposto 1con munizioni finali95
- ...
Note di chiusura
Quindi, spero di aver reso la mia soluzione più difficile da battere, ma PER FAVORE PROVA! Usalo contro di me, aggiungi qualche elaborazione parallela, una migliore teoria dei grafi, ecc. Un paio di cose che immagino potrebbero migliorare questo approccio:
- "ridurre" in modo aggressivo i loop per eliminare la ricostruzione inutile man mano che l'algoritmo avanza.
- Un esempio: nel problema di esempio, considera i loop 1-2-3 e le altre permutazioni come "un passo", in modo da poter fare il nostro ciclo di fine ciclo più rapidamente.
- Ad esempio, se ci si trova nel nodo 1, è possibile (a) passare a 2, (b) passare a 1, (c) passare 1-2-3 come un passo e così via. Ciò consentirebbe a un risolto di ripiegare la profondità in larghezza, aumentando il numero di percorsi a una profondità particolare ma accelerando notevolmente il tempo di soluzione per i cicli lunghi.
abbattere percorsi morti. La mia attuale soluzione non "ricorda" che un determinato percorso è senza uscita e deve riscoprirlo ogni volta. Sarebbe meglio tenere traccia del primo momento in un percorso in cui la morte è certa e non progredire mai oltre. fatto...
- se attento, è possibile applicare l'abbattimento della rotta morta come abbattimento della sotto-rotta. Ad esempio, se 1-2-3-4 provoca sempre la morte e il risolutore sta per testare la rotta 1-3-1-2-3-4, dovrebbe immediatamente smettere di scendere quella via poiché si garantisce che finirà nella delusione. Sarebbe comunque possibile calcolare il numero di uccisioni, con un po 'di matematica attenta.
Qualsiasi altra soluzione che scambia memoria per tempo o consente di evitare aggressivamente le seguenti rotte senza uscita. fatto anche questo!
1inizia con 0 munizioni? Il grafico non è direzionale?