Java ( менше гротеск: 8415 5291 3301)
Добре. В основному, мені бентежено, що ніхто не подав рішення. Тому кілька днів тому я почав намагатися вирішити цю проблему, але це чудово. . Перейдіть за цим посиланням, щоб спостерігати за моїм прогресом на ньому через GitHub.
Редагувати
Нова версія solver, набагато більше "гольф", з виправленою контрольною циклом, яку ідентифікує MT0. Він також підтримує маршрути швидкого переадресації, налаштовуючи на зміну кількості пам'яті, доступної для VM. Останнє редагування BIG : зрозумів, що у мене було кілька інших невеликих помилок індексу та передчасної оптимізації, що призвело до неврахування досить великої кількості видів виграшів. Отже, це ретельно, ретельно. Нова версія як менша, так і нижче. Для нашого довідкового маршруту java -Xmx2GB ZombieHordeMin
ця штука є досить приємною (попередити, це займе певний час).
Класний фактоїд
У захоплюючому повороті є МНОГО рішень довжиною 24, і мій вирішувач знаходить одне, що відрізняється від MT0, але принципово ідентичне, за винятком того, що воно починається з відвідування інших підключених до них форпостів 1
. Захоплююче! Повністю протидіє людській інтуїції, але цілком справедливо.
Основні моменти рішення
Так ось моя. Це (частково) гольф, в / с - це експоненціальний, майже грубе рішення. Я використовую алгоритм IDDFS (перший ітераційний поглиблення глибини першого пошуку), тому це чудовий загальний вирішувач, який не пропускається, тому він вирішує обидві частини питання ОП, а саме:
- Якщо знайдений виграшний маршрут (нескінченне зомбі), виведіть "x".
- Якщо всі маршрути закінчуються смертю (кінцеві зомбі), виведіть найбільшу кількість вбитих зомбі.
Дайте йому достатньо енергії, пам’яті та часу, і вона зробить саме це, навіть мапи повільної смерті. Я витратив трохи більше часу на вдосконалення цього рішення, і хоча можна зробити більше, зараз це трохи краще. Я також інтегрував поради MT0 щодо найкращого рішення щодо нескінченних зомбі та видалив декілька передчасних оптимізацій з моєї виграшної перевірки, яка заважала попередній версії знайти її, і тепер я фактично знаходжу дуже подібне рішення до описаного MT0.
Кілька інших важливих моментів:
- Як вже було сказано, IDDFS використовує для пошуку найкоротшого виграшного маршруту.
- Оскільки він є основою DFS, він також виявить, чи закінчується кожен маршрут смертю нашого героя, і відслідковує "найкращий" маршрут з точки зору більшості загиблих зомбі. Помер герой!
Я інструментував алгоритм, щоб зробити його цікавішим дивитися Removed для цілей гольфу. Перейдіть за одним із посилань на github, щоб побачити версію, яка не перебуває у віці.
Також є чимало коментарів, тому сміливо повторно впроваджуйте для власного рішення рішення на основі мого підходу або покажіть мені, як це слід робити!
- Швидке перемотування по маршруту, адаптоване до пам'яті
- До наявної системної пам’яті буде відслідковуватися «кінцеві маршрути», які не призвели до смерті.
- Використовуючи стильний режим стиснення та декомпресії маршруту, відновлюється прогрес попередньої ітерації IDDFS, щоб запобігти повторному виявленню всіх попередньо відвіданих маршрутів.
- Як навмисний бонусний бонус, виступає як тупик маршруту. Маршрути в глухий кут не зберігаються і більше ніколи не відвідуватимуться в глибинах IDDFS.
Історія вирішувача
- Я спробував купу алгоритмів перегляду в один крок, і хоча для дуже простих сценаріїв вони працюватимуть, зрештою вони виходять з ладу.
- Тоді я спробував двокроковий алгоритм перегляду вперед, який був ... незадовільним.
- Тоді я почав будувати n-ступінчастий підхід, коли зрозумів, що такий підхід можна привести до DFS, але DFS набагато елегантніший.
- Під час створення DFS мені спало на думку, що IDDFS забезпечить (a) пошук найкращого маршруту HERO (смерть) або (b) перший переможний цикл.
- Виявляється, побудувати шашку виграшного циклу легко, але мені довелося пройти кілька дуже неправильних ітерацій, перш ніж я потрапив до успішно перевіреної шашки.
- Здійснено фактор у виграшному шляху MT0, щоб усунути три лінії передчасної оптимізації, що зробило мій алгоритм сліпим до нього.
- Додано адаптивний алгоритм кешування маршрутів, який буде використовувати всю пам'ять, яку ви йому надаєте, щоб запобігти непотрібному переробленню роботи між дзвінками IDDFS, а також скидає тупикові маршрути до меж пам'яті.
(Гольф) код
Увімкніть код (отримайте версію без заготівлі тут чи тут ):
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);}}
Отримайте код від github тут, щоб відстежувати будь-які зміни, які я вношу. Ось деякі інші карти, якими я користувався.
Вихідний приклад
Приклад виводу для еталонного рішення:
$ 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
Прочитайте вихідний маршрут так step
:: source
, route-to-get-here
- ammo
. Отже, у наведеному вище рішенні ви читали б це:
- На сході
0
, на заставі 1
з боєприпасами 100
.
- На кроці
1
скористайтеся маршрутом, 1
щоб дістатися до застави 3
із закінченням боєприпасів97
- На кроці
2
скористайтеся маршрутом, 1
щоб дістатися до застави 1
із закінченням боєприпасів95
- ...
Заключні записки
Отож, я сподіваюся, що я зробив своє рішення важче перемогти, але ЗАБУДУЙТЕ! Використовуйте це проти мене, додайте паралельну обробку, кращу теорію графіків тощо. Декілька речей, які я думаю, можуть покращити такий підхід:
- агресивно "зменшувати" петлі, щоб вирізати непотрібне оновлення під час просування алгоритму.
- Приклад: у прикладі проблеми розглядайте петлі 1-2-3 та інші перестановки як "один крок", щоб ми могли швидше пройти шлях до кінця циклу.
- Наприклад, якщо ви знаходитесь у вузлі 1, ви можете (a) перейти до 2, (b) перейти до 1, (c) пройти 1-2-3 як один крок тощо. Це дозволило б вирішити скласти глибину в ширину, збільшивши кількість маршрутів на певній глибині, але значно пришвидшивши час до вирішення довгих циклів.
вибити мертві маршрути. Моє поточне рішення не «пам’ятає», що певний маршрут тупиковий, і його потрібно кожного разу переоткривати. Краще було б відслідковувати найдавніший момент у маршруті, за яким смерть є певною, і ніколи не прогресувати за нею. зробив це ...
- якщо обережно, ви можете застосувати мертве відсікання маршруту як підключення маршруту. Наприклад, якщо 1-2-3-4 завжди призводить до смерті, а вирішувач збирається перевірити маршрут 1-3-1-2-3-4, він повинен негайно припинити спуск по цьому шляху, оскільки він гарантовано закінчиться в розчаруванні. Ще можна було б порахувати кількість кілограмів, дотримуючись ретельної математики.
Будь-яке інше рішення, яке торгує пам'яттю протягом часу або дозволяє агресивно уникати наступних маршрутів. зробив це теж!
1
починається з 0 боєприпасів? Чи графік непрямий?