Заключний стенд - перемогти Орду зомбі


25

Вступ

Ви самі на острові. Залишок людства загинув ( ймовірно, через помилку в коді користувача12345 ). Піратські орди Зомбі дійшли до вашого острова, і вони нескінченні. Настав час бити попкою або жувати жувальну гумку, і ви все перестали гуляти.

Проблема

Наш сценарій кінцевого дня описується двома цілими числами в одному рядку mта n. На вашому острові форпости, однозначно пронумеровані від 1 до m. Наступні nрядки містять кожні три цілих числа, x, y, і z, розділений пробіл. xі yє унікальними ідентифікаторами двох форпостів, і zце кількість зомбі, які будуть зустрічатися на шляху між ними.

Під час подорожі стежкою ви втрачаєте zбоєприпаси і вбиваєте zзомбі. Якщо ви знову пройдетесь по тій же стежці, ви, на жаль, зустрінете однакову кількість зомбі. Усі застави генерують +1 боєприпаси щоразу, коли ви проїжджаєте шлях. Ви починаєте зі 100 боєприпасів на заставі 1. Усі застави починаються з 0 боєприпасів. Ви гинете відразу, якщо не існує шляху, для якого ваша амуніція перевищує кількість зомбі на цьому шляху, а решта ваших боєприпасів перетворюється на вбивства. Така ваша фінальна позиція.

Напишіть програму, яка виводить максимальну кількість зомбі, яких можна вбити для заданого сценарію. Якщо ви можете вбити нескінченну кількість зомбі, просто виведіть x.

Приклад введення

5 6
1 2 4
2 3 4
3 1 4
2 4 10
2 5 10
1 1 50

Приклад Вихід

x

Припущення

  • Шлях буде між двома дійсними форпостами. Тобто 1 <= x/ y<=m
  • Якщо шлях між xі yне вказаний, його не можна пропустити
  • Шлях є двонаправленим
  • 1 m<<= 100
  • 1 n<<= 500
  • Вхід повинен бути наданий через stdin, прочитаний з файлу або прийнятий як єдиний аргумент програми, і він повинен точно відповідати формату прикладу
  • Час виконання вашої програми може бути довільно великим, але має бути визначено кінцевим

Код з найменшими символами виграє!


Чи кожен форпост, окрім цього, 1починається з 0 боєприпасів? Чи графік непрямий?
Пітер Тейлор

2
Можливо, також було б корисно попередньо виправити певний клас помилок, встановивши тестовий випадок, у якому існує цикл, який не запускає боєприпаси, але якого неможливо досягти вчасно. (Варто додати, що я не переконаний, що поточний тестовий випадок є правильним: мені здається, що цикл 1->1коштує 49 боєприпасів, а цикл 1->2->3->1коштує 3 боєприпаси в довгостроковій перспективі.
Пітер Тейлор

@PeterTaylor Мені довелося відхилити обидва мої коментарі, оскільки, схоже, я зробив приклад двонаправленим . Тож дозвольте мені почати спочатку - всі шляхи двосторонні, а всі застави починаються з 0. Приклад повинен працювати.
Rainbolt

@Rusher: Гарний приклад! Зробив мені 45 кроків, щоб показати себе, що це дійсно безмежно стійкий. Чи можемо ми припустити, що всі застави будуть доступними або ви хочете, щоб ми розглядали той випадок, коли застави відключені від основного графіка?
Клавдіу

1
Аааа ... Отже, на кожному кроці від А до В кожен застава "генерує" патрони і зберігає його там, поки ви не відвідаєте його.
Тобія

Відповіді:


14

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, він повинен негайно припинити спуск по цьому шляху, оскільки він гарантовано закінчиться в розчаруванні. Ще можна було б порахувати кількість кілограмів, дотримуючись ретельної математики.
  • Будь-яке інше рішення, яке торгує пам'яттю протягом часу або дозволяє агресивно уникати наступних маршрутів. зробив це теж!

Гарна відповідь! Кому потрібно розіграти свій код, коли вони єдині, хто може вирішити проблему? Зараз я мотивований написати власне рішення, тому над цим працюю.
Rainbolt

Чудово, саме це я сподівався. Не соромтеся позичати / вкрасти що-небудь із моєї відповіді, що вам здається корисним! Хоча, звичайно, сподіваюся, що інші люди, а не лише я та ОП, намагатимуться вирішити: P
ПрограмістDan

Я перейшов на бік і почав мінімізувати ваш код. Якщо ви вважали, що ваша відповідь раніше була гротескною, перевірте це: tny.cz/17ef0b3a . Ще триває робота.
Rainbolt

Ха-ха, ти насправді натрапив на бік. Дивлячись добре (доречно жахливо для коду-гольфу? Ви знаєте, що я маю на увазі) поки що!
ПрограмістDan

@Rusher Будь-яка удача поки що? У мене є кілька ідей щодо вдосконалень, які я готував, включаючи техніку стиснення подання маршруту та спосіб швидкого перемотування вперед вже обробленими маршрутами (до точки).
ПрограмістDan

2

Деякі абстрактні примітки до рішення

Якщо я отримаю час, я перетворять це в алгоритм ...

Для даного графіка Gтоді існує підключений під графік, G'який містить місто 1. Якщо існує нескінченна рішення , то буде існувати підключений підграф G''з G'який містить Vміста і Pшлях.

Шляхи Pз G''може бути розділена таким чином, що {p}містить шлях , який має мінімальну вартість всіх шляхів в Pі P/{p}є всіма іншими шляхами (що утворюють сполучного дерева або , можливо , циклу). Якщо припустити , що pце не зациклення край (з'єднує обидва кінці в тому ж місті) , то він з'єднає два міста ( v1а v2) і має вартість cбоєприпасів , то ви (у зв'язку з втратою годувальника) може потім траверс від v1до v2і назад в загальній вартості 2cбоєприпасів і це збільшить боєприпаси у всіх містах на 2 (для загального збільшення в 2|V|межах G''- частина з яких буде зібрана з v1та v2).

Якщо ви подорожуєте з v1до v2і назад v1кілька ( m) раз , а потім відправитися в подорож з v1по краях , P/{p}щоб відвідати всі , крім міст v1і , v2перш ніж повернутися , v1і це займе nшляху досягнення (де , |P/{p}| ≤ n ≤ 2|P/{p}|так як ви ніколи не повинні пройти шлях більш ніж удвічі) з вартістю kі міста отримають 2m|V|боєприпаси (знову частина з них буде зібрана під час обходу).

З огляду на все це, то ви можете сказати, чи можливе нескінченне рішення, якщо тоді вартість k + 2mcдорівнює або нижче загальної винагороди 2(m+n)|V|.

Проблема полягає в тому, що:

  • можливо, вам доведеться подорожувати зі стартового міста 1до {p}першої ітерації і потрібно враховувати цю вартість; і
  • Ви також повинні переконатися, що mта nчи достатньо низькі, щоб у Вас не закінчилося боєприпасів, перш ніж Ви зможете зробити це через першу ітерацію, оскільки перша ітерація матиме більшу вартість, ніж наступні ітерації).

Це призводить до нейтрального вирішення на прикладі запитання (чисельність - кількість відвідуваних міст) на 24 шляху:

1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,2,4,2,5,2,3, ... and repeat ...

Ще одна невелика річ - вам, можливо, доведеться розглянути цілісні ребра із вартістю 1, оскільки самі ці краї формують виграшну умову, якщо ви зможете їх досягти вчасно.
Rainbolt
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.