Final Stand - Zombie Horde'u Yenin


25

Giriş

Bir adada yalnızsın. İnsanlığın geri kalanı öldü ( muhtemelen kullanıcı12345 kodundaki hatadan dolayı ). Zombie Pirate Horde adanıza ulaştı ve sonsuzlar. Kıç tekmeleme ya da ciklet çiğneme zamanı geldi ve hepiniz cikletten uzaksınız.

Sorun

Kıyamet günü senaryomuz, tek bir satırda 2 tamsayı ile tanımlanır mve n. Adanızda 1'den 1'e kadar benzersiz numaralar vardır m. Aşağıdaki nçizgiler, her üç tamsayı içerir x, yve z, bir boşluk ile birbirlerinden ayrılmıştır. xve yiki çıkışın benzersiz kimlikleridir ve zaralarındaki yolda karşılaşılacak olan zombilerin sayısıdır.

Bir yola seyahat ederken zcephanenizi kaybedersiniz ve zzombileri öldürürsünüz . Yine aynı yolu izlerseniz, ne yazık ki aynı sayıda zombiyle karşılaşırsınız. Tüm çıkışlar, her yola seyahat ettiğinizde +1 mühimmat oluşturur. Üst karakol 1'de 100 mühimmatla başlarsınız. Tüm mühimmatlar 0 mühimmatla başlar. Mühimmatınızın o yoldaki zombi sayısından daha büyük olmadığı ve mühimmatınızın geri kalanının öldürülmeye dönüştürüldüğü bir yol yoksa hemen ölürsünüz. Bu senin son duruşun.

Belirli bir senaryo için öldürebileceğiniz maksimum zombi sayısını gösteren bir program yazın. Sonsuz sayıda zombiyi öldürebilirseniz, basitçe çıkış yapın x.

Örnek Giriş

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

Örnek çıktı

x

Varsayımlar

  • Bir yol iki geçerli çıkış arasında olacaktır. Yani 1 <= x/ y<=m
  • Arasında xve ylistelenmemiş bir yol varsa , seyahat edilemez
  • Bir yol çift yönlüdür
  • 1 << m= 100
  • 1 << n= 500
  • Girdiler stdin aracılığıyla sağlanmalı, bir dosyadan okunmalı veya programın tek bir argümanı olarak kabul edilmeli ve örneğin formatını tam olarak izlemelidir.
  • Programınızın çalışma zamanı isteğe bağlı olarak büyük olabilir ancak belirlenebilir derecede sınırlı olmalıdır

En az karakter içeren kod kazanır!


Her karakol 10 mühimmat ile başlamak dışında mı? Grafik tek yönlü mü?
Peter Taylor

2
Muhtemelen, cephanenizi akmayan ve zamanla ulaşılamayan bir döngü olduğu bir test vakasına sahip olarak, belirli bir böcek sınıfını ön-boşaltma da faydalı olacaktır. (Mevcut test durumunun doğru olduğuna ikna olmadığımı da eklemeliyim: bana göre bu döngü 1->149 cephaneye mal oluyor ve döngü 1->2->3->1uzun vadede 3 cephaneye mal oluyor.
Peter Taylor

@PeterTaylor Her iki yorumumu da geri çekmek zorunda kaldım, çünkü iki yönlü örnek yaptım . Bu yüzden baştan başlamama izin verin - tüm yollar çift yönlüdür ve tüm çıkışlar 0 ile başlar. Örnek şimdi çalışmalıdır.
Rainbolt

@Rusher: Güzel örnek! Kendime gerçekten de sürdürülebilir olduğunu göstermek için 45 adım attı. Tüm çıkışların ulaşılabilir olacağını varsayabilir miyiz ya da ana grafikle bağlantısı kesilmiş çıkışların olduğu durumu ele almamızı ister misiniz?
Claudiu

1
Ahhh ... Yani A'dan B'ye her adım için, her karakol bir cephane “oluşturur” ve siz ziyaret edene kadar orada tutar.
Tobia,

Yanıtlar:


14

Java ( daha az grotesk: 8415 5291 3301)

Tamam. Temel olarak, hiç kimsenin bir çözüm yollamadığı için utanıyorum. Bu yüzden birkaç gün önce bu sorunu çözmeye çalıştım, b / c harika. . GitHub ile ilerlememi izlemek için bu linki takip edin.

Düzenle

Yeni çözücü sürümü, MT0 tarafından tanımlandığı şekilde düzeltilmiş döngü kontrolcüsü ile çok daha "golf oyunu". Ayrıca, VM için ne kadar bellek kullanılabilir olduğunu değiştirerek ayarlanabilen hızlı ileri sarma rotalarını da destekler. En son BÜYÜK düzenleme: birkaç küçük indeks hatası ve erken optimizasyonlar olduğumu fark ettim, bu da oldukça fazla sayıda kazanımın dikkate alınmamasıyla sonuçlandı. Demek dikkatlice düzeltildi. Yeni sürüm hem daha küçük hem de daha küçük. Referans rotamız java -Xmx2GB ZombieHordeMiniçin püf noktası oldukça iyi yapıyor (uyarılmalıdır, biraz zaman alacaktır).

Serin factoid

Büyüleyici bir bükülmede, 24 uzunluğundaki MANY çözümleri var ve çözücüm, MT0'lardan farklı bir tane buluyor, ancak prensip olarak aynı , diğer bağlantı noktalarını ziyaret ederek başlaması dışında 1. Büyüleyici! Tamamen insan sezgisine karşı, ama tamamen geçerli.

Çözümün Önemli Noktaları

İşte benim. (Kısmen) golf oynamak, b / c üstel, neredeyse kaba kuvvetli bir çözücü. Bir IDDFS (yinelemeli derinlik derinliği ilk arama) algoritması kullanıyorum, bu yüzden atlamayan harika bir genel çözücü, bu yüzden OP'nin sorusunun her iki bölümünü de çözüyor :

  • Kazanan bir rota bulunursa (sonsuz zombiler), 'x' çıktısı alın.
  • Tüm yollar ölümle bitiyorsa (sonlu zombiler), öldürülen en fazla sayıda zombi ortaya çıkar.

Yeterince güç, hafıza ve zaman verin, sadece yavaş ölüm haritalarını bile yapacak. Bu çözücüyü geliştirmek için biraz daha zaman harcadım ve daha fazlası yapılabilirken, şimdi biraz daha iyi. Ayrıca MT0'ın en iyi sonsuz-zombiler çözümü konusundaki tavsiyelerini birleştirdim ve önceki sürümün bulmasını engelleyen kazancımdan birkaç erken optimizasyon aldım ve şimdi tarif edilen MT0 ile benzer bir çözüm buldum.

Birkaç diğer önemli özellik:

  • Belirtildiği gibi, mümkün olan en kısa kazanma rotasını bulmak için bir IDDFS kullanır.
  • Özünde bir DFS olduğu için, her rotanın kahramanımızın ölümüyle sona erip bitmediğini ve öldürülen çoğu zombide "en iyi" rotayı takip edip etmediğini de keşfedecektir. Bir kahraman öl!
  • Golf amaçlı, Removed izlemesini daha ilginç kılmak için algoritmayı geliştirdim . Ungolded versiyonunu görmek için github linklerinden birini takip edin.
  • Ayrıca, bir takım yorumlar da var, bu yüzden yaklaşımımla ilgili kendi çözüm kurgunuz için yeniden uygulamaktan çekinmeyin ya da nasıl yapılması gerektiğini gösterin!
  • Hafızaya uyumlu rota hızlı ileri sarma
    • Kullanılabilir sistem hafızasına kadar, ölümle sonuçlanmayan "bitiş yollarını" izleyecektir.
    • Süslü bir rota sıkıştırma ve dekompresyon rutini kullanarak, daha önce ziyaret edilen tüm rotaların yeniden keşfedilmesini önlemek için önceki bir IDDFS yinelemesinden elde edilen ilerleme geri yüklenir.
    • Kasıtlı bir yan bonus olarak, çıkmaz bir rota ayrıcısı olarak davranır. Çıkmaz yollar saklanmaz ve gelecekteki IDDFS derinliklerinde bir daha asla ziyaret edilmeyecektir.

Çözücünün tarihi

  • Birkaç adım ileriye dönük bir algoritma denedim ve çok basit senaryolar için çalışırlarsa, sonuçta düz düşerler.
  • Sonra iki adım ileriye dönük bir algoritma denedim, ki bu tatmin edici değildi.
  • Daha sonra, bu yaklaşımın DFS'ye indirgenebilir olduğunu anladığımda n adımlı bir bakış açısı oluşturmaya başladım, ancak DFS çok daha ... daha zarif.
  • DFS'yi oluştururken, IDDFS'nin (a) en iyi HERO (ölüm) yolunu veya (b) ilk kazanan çevrimi bulmasını sağlayacağı aklıma geldi.
  • Kazanma döngüsü denetleyicisi oluşturmanın kolay olduğu, ancak başarılı bir denetleyiciye ulaşmadan önce birkaç çok yanlış yinelemeden geçmek zorunda kaldım.
  • Algoritmamın kör etmesine neden olan üç erken erken optimizasyon çizgisini kaldırmak için MT0'ın kazandığı yolda etkili oldu.
  • IDDFS çağrıları arasında gereksiz yere yeniden çalışmayı önlemek için verdiğiniz tüm belleği kullanacak ve ayrıca çıkmaz rotaları bellek sınırlarına kadar kaldıracak bir adaptif rota önbellekleme algoritması eklendi.

(Golf) kodu

Kodda (ungolfed sürümünü buradan veya buradan alın ):

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);}}

Yaptığım değişiklikleri izlemek için, burada github'dan kodu alın . İşte kullandığım diğer haritalar.

Çıktı örneği

Referans çözümü için örnek çıktı:

    $ 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

Bu şekilde rota çıkışını okuyun step:: source, route-to-get-here- ammo. Yani yukarıdaki çözümde şöyle okursunuz:

  • Adımda 0, 1cephane ile karakolda 100.
  • Adımda 1, cephane 1bitene kadar karakol bulmak için rotayı kullanın397
  • Adımda 2, cephane 1bitene kadar karakol bulmak için rotayı kullanın195
  • ...

Kapanış notları

Bu yüzden umarım çözümümü yenmeyi zorlaştırdım, ancak LÜTFEN LÜTFEN! Bana karşı kullanın, bazı paralel işlemlere, daha iyi grafik teorisine vb. Ekleyin. Bu yaklaşımı geliştirebileceğimi düşündüğüm birkaç şey :

  • algoritma ilerledikçe agresif bir şekilde "azaltma" döngülerine gerek duymadan geriye çekilmeyi keser.
    • Bir örnek: Örnek problemde, 1-2-3 döngülerine ve diğer permütasyonları "bir adım" olarak düşünün, böylece daha hızlı bir şekilde döngü sonlandırmak için yolumuzu yapabiliriz.
    • Örneğin, 1. düğümdeyseniz, (a) 2'ye, (b) 1'e, (c) 1-2-3'ten bir adım olarak geçebilirsiniz. Bu, bir çözücünün derinliği genişliğe katlamasına izin vererek, belirli bir derinlikte rota sayısını artırarak uzun döngüler için çözüme gitme süresini büyük ölçüde hızlandırır.
  • cull ölü yollar. Mevcut çözümüm, belirli bir yolun sonlandırıldığını ve her seferinde yeniden keşfetmesi gerektiğini "hatırlamıyor". En erken anı ölümün kesin olduğu ve asla ilerlemeyeceği bir rotada takip etmek daha iyi olacaktır. bunu yaptı...
  • Dikkatli olursanız, ölü yol boşluğu alt yol boşluğu olarak uygulayabilirsiniz. Örneğin, 1-2-3-4 her zaman ölümle sonuçlanırsa ve çözücü 1-3-1-2-3-4 güzergahını test etmek üzereyse, sona ermesini garanti ettiği için o yolu düşürmeyi derhal durdurmalıdır. hayal kırıklığı içinde. Dikkatli bir matematikle öldürme sayısını hesaplamak yine de mümkün olacaktı.
  • Belleği zamana saran ya da takip eden çıkmaz rotaların saldırgan bir şekilde kaçınmasına izin veren başka herhangi bir çözüm. Bunu da yaptım!

Güzel cevap! Sorunu çözebilecek tek kişi olduklarında kodlarını kim atmalı? Şimdi kendi çözümümü yazmak için motive oluyorum, bu yüzden üzerinde çalışıyorum.
Rainbolt

Mükemmel, bunun olacağını umduğum şeydi. Cevabımdan yararlı bulduğunuz her şeyi ödünç almaktan / çalmaktan çekinmeyin! Tabii ki sadece kendimden ve OP'den başkalarının çözmeyi deneyeceğini umuyorum: P
ProgrammerDan

Yanıma saptım ve kodunu küçültmeye başladım. Cevabınız daha önce grotesk olduğunu düşünüyorsanız, bu göz atın : tny.cz/17ef0b3a . Hala devam eden bir çalışma.
Rainbolt

Haha, gerçekten yanıltıldın. İyi görünüyorsun (kod-golf için uygun bir şekilde korkunç mu? Ne demek istediğimi biliyorsun)!
ProgramcıDan

@Rusher Şu ana kadar herhangi bir şans var mı? Bir rota gösterimi sıkıştırma tekniği ve önceden işlenmiş rotalarda (bir noktaya kadar) ileri sarmanın bir yolu dahil olmak üzere, ürettiğim gelişmeler için birkaç fikrim var.
ProgrammerDan

2

Bir çözüm üzerine bazı soyut notlar

Zamanım olursa bunu bir algoritmaya dönüştüreceğim ...

Belirli bir grafik için , şehri içeren Gbağlı bir alt grafik G'vardır 1. Sonsuz bir çözüm ise, o zaman, bağlı bir alt grafik var olacaktır G''ait G'içeren Vkasaba ve Pyolları.

Yollar Parasında G''olacak şekilde bölümlenmiş olabilir {p}tüm yolların minimal maliyeti olan bir yolu içerir Pve P/{p}(bir kapsayan ağaç ya da muhtemelen bir döngü oluşturan) tüm diğer yollar olduğunu. Biz o varsayarsak po zaman iki kasaba (bağlayacak (aynı şehre iki ucunu bağlayan) bir döngü kenar değildir v1ve v2) ve maliyet vardır cçapraz gelen sonra o zaman (kurtulan) mühimmat olabilir v1için v2toplam maliyetle ve arka 2ccephane ve bu (toplam artışın 2 bütün kasabalarda mühimmat artacak 2|V|içinde G''- bunlardan bazıları toplanan olacaktır v1ve v2).

Eğer gelen seyahat ediyorsanız v1etmek v2ve geri v1(birden msüreleri) ve daha sonra gelen bir yolculuğa çıkalım v1kenarlar boyunca P/{p}dışındaki tüm şehirleri ziyaret etmek v1ve v2geri dönmeden önce v1ve bu sürer n(ulaşmak için yolları nerede |P/{p}| ≤ n ≤ 2|P/{p}|daha bir yol boyunca hareket etmesi gerekli asla beri iki katından daha fazla) bir maliyetle kve şehirler 2m|V|mühimmat kazanacak (yine bazıları kesişme sırasında toplanacak).

Tüm bunlar göz önüne alındığında, o zaman maliyetin k + 2mctoplam ödülden eşit veya düşük olması durumunda sonsuz bir çözümün olası olup olmadığını anlayabilirsiniz 2(m+n)|V|.

Buradaki problemin ek bir karmaşıklığı var:

  • Eğer başlangıç kasabadan seyahat etmek gerekebilir 1için {p}bu maliyet faktörünün ilk yineleme ve ihtiyaca; ve
  • Ayrıca , ilk yinelemenin sonraki yinelemelerden daha yüksek bir maliyeti olacağından, ilk yinelemeden önce, cephanenizin tükenmediğinden mve nyeterince düşük olduğundan emin olmanız gerekir.

Bu, söz konusu örnekte 24 yol maliyetli bir tarafsız çözüme yol açar (sayılar ziyaret edilen şehirlerdir):

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 ...

Eklemek için küçük bir şey - Eğer 1 zaman ile döngü kenarları düşünmek zorunda kalabilirsiniz, çünkü zaman içinde ulaşabiliyorsanız bu kenarlar tek başına bir kazanma koşulu oluşturur.
Rainbolt
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.