最終スタンド-ゾンビ大群を倒す


25

前書き

あなたは島に一人でいます。人類の残りは死んでいます(おそらくuser12345のコードのバグが原因です))。ゾンビ海賊大群はあなたの島に到着しました、そして、彼らは無限です。それはお尻を蹴ったりバブルガムをかむ時であり、あなたはすべてバブルガムから出ています。

問題

私たちの最悪のシナリオは、単一の行に2つの整数で説明し、されるmn。島には、1からの一意の番号が付けられた前post基地がありますm。次nの行はそれぞれ、三つの整数は、含まれているxyz、スペースで区切られています。xyは、2つの前post基地の一意のIDでzあり、それらの間のパスで遭遇するゾンビの数です。

パスを移動すると、z弾薬を失い、zゾンビを殺します。再び同じ道を進むと、残念ながら同じ数のゾンビに遭遇します。すべての前post基地は、パスを移動するたびに+1弾薬を生成します。前post1の弾薬100個で開始します。すべての前Allは弾薬0個で始まります。あなたの弾薬がそのパス上のゾンビの数よりも大きいパスが存在しない場合、あなたはすぐに死に、あなたの弾薬の残りはキルに変換されます。これが最終的な立場です。

特定のシナリオで殺すことができるゾンビの最大数を出力するプログラムを作成します。無限の数のゾンビを殺すことができるなら、単に出力してくださいx

入力例

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

出力例

x

仮定

  • パスは2つの有効な前post地の間にあります。つまり、1 <= x/ y<=m
  • xとの間のパスがyリストされていない場合、移動できません
  • パスは双方向です
  • 1 << m= 100
  • 1 << n= 500
  • 入力は、stdinを介して提供されるか、ファイルから読み取られるか、プログラムへの唯一の引数として受け入れられ、例の形式に正確に従う必要があります。
  • プログラムの実行時間は任意に大きくできますが、決定的に有限でなければなりません

文字数が最も少ないコードが勝ちです!


各前post基地は1弾薬数0から始まりますか?グラフは無指向性ですか?
ピーターテイラー14年

2
弾薬を使い果たしてはいないが、時間内に到達できないサイクルがあるテストケースを作成することで、特定のクラスのバグを先取りすることもおそらく役立つでしょう。(現在のテストケースが正しいと確信していないことを付け加えます。サイクルに1->1は49弾薬が1->2->3->1かかり、長期的には3弾薬になると思われます。
Peter Taylor 14年

@PeterTaylor例を双方向にしたように見えるので、両方のコメントを撤回しなければなりませんでした。したがって、最初からやり直します-すべてのパスは双方向であり、すべての前postは0で始まります。例はこれで動作するはずです。
Rainbolt

@Rusher:良い例です!45のステップを踏んで、実際に無限に持続可能であることを証明しました。すべての前post地に到達できると想定できますか、またはメイングラフから切断された前post地がある場合の処理​​を希望しますか?
クラウディ14年

1
ああ... AからBまでの各ステップで、すべての前post基地は弾薬を「生成」し、訪問するまでそこに保管します。
トビア14

回答:


14

Java(グロテスクでない8415 5291 3301)

OK。基本的に、誰も解決策を提出していないのは恥ずかしいです。だから数日前、私はこの問題を解決しようとし始めました、それは素晴らしいことです。。そのリンクをたどって、GitHubで進捗状況を確認してください。

編集

MT0によって識別される修正されたサイクルチェッカーを備えた、新しい「ゴルフ」バージョンのソルバー。また、VMで使用可能なメモリ量を変更することで調整可能な、高速転送ルートもサポートしています。最新のBIG編集:他にもいくつかの小さなインデックスエラーと時期尚早な最適化があり、非常に多くの種類の勝利を検討できなかったことに気付きました。だから、それは慎重に修正されました。新しいバージョンはより小さく、以下の両方です。参照ルートでjava -Xmx2GB ZombieHordeMinは、このトリックは非常にうまく機能します(注意してください、しばらく時間がかかります)。

クールなファクトイド

魅力的なねじれでは、長さが24で多くのソリューションがあり、そして私のソルバーは、それがあることを除いて、MT0の異なるが、原則的に同じものを見つけ始まりに接続された他の前哨基地を訪問して1。魅力的!人間の直感に完全に対抗しますが、完全に有効です。

ソリューションのハイライト

これが私のものです。それは(部分的に)ゴルフされ、b / cそれは指数関数的で、ほとんど力にならないソルバーです。IDDFS(反復深化深さ優先検索)アルゴリズムを使用しているため、スキップしない優れた一般的なソルバーであるため、OPの質問の両方の部分を解決します。

  • 勝ちのルートが見つかった場合(無限のゾンビ)、「x」を出力します。
  • すべてのルートが死んでいる場合(有限ゾンビ)、殺されたゾンビの最大数を出力します。

十分なパワー、メモリ、および時間を与えてください。そうすれば、スローデスマップであってもそれができます。私はこのソルバーを改善するためにもう少し時間を費やしましたが、もっとできることはありますが、今は少し良くなっています。また、最高の無限ゾンビソリューションに関するMT0のアドバイスを統合し、以前のバージョンがそれを見つけるのを妨げていたwin-checkerからいくつかの時期尚早な最適化を削除しました。

他のいくつかのハイライト:

  • 前述のように、IDDFSを使用して、可能な限り最短のルートを見つけます。
  • それはDFSの中核であるため、すべてのルートがヒーローの死で終わるかどうかも発見し、殺されたほとんどのゾンビに関して「最適な」ルートを追跡します。ヒーローを死にます!
  • 私はアルゴリズムをインストルメント化して、ゴルフの目的でRemoved を見るのをより面白くしました。GitHubへのリンクのいずれかをたどって、未使用バージョンを確認してください。
  • 全体にわたって多くのコメントがありますので、私のアプローチに基づいて独自のソリューションを再実装したり、どのように行うべきかを教えてください!
  • メモリ対応ルートの早送り
    • 使用可能なシステムメモリまで、死に至らなかった「終了ルート」を追跡します。
    • 高度なルート圧縮および圧縮解除ルーチンを使用して、IDDFSの以前の反復からの進捗が復元され、以前にアクセスしたすべてのルートが再検出されないようにします。
    • 意図的な副ボーナスとして、行き止まりのルートカリングとして機能します。行き止まりのルートは保存されず、IDDFSの将来の深さで再びアクセスされることはありません。

ソルバーの歴史

  • ワンステップ先読みアルゴリズムの束を試しましたが、非常に単純なシナリオでは機能しますが、最終的には平坦になります。
  • 次に、2段階の先読みアルゴリズムを試してみましたが、満足できませんでした。
  • その後、このアプローチがDFSに還元可能であると認識したときに、nステップ先読みの構築を開始しましたが、DFSははるかにエレガントです。
  • DFSの構築中に、IDDFSが(a)最良のHERO(死亡)ルートを見つけるか、(b)最初の勝利サイクルを確保することに気付きました。
  • win-cycleチェッカーの作成は簡単ですが、成功するチェッカーにたどり着くまでに、非常に間違った繰り返しを何度か実行する必要がありました。
  • MT0のwin-pathを考慮に入れて、3行の早すぎる最適化を削除して、アルゴリズムを盲目にしました。
  • IDDFS呼び出し間の不要な作業のやり直しを防ぐために、指定したすべてのメモリを使用する適応型のルートキャッシングアルゴリズムを追加し、メモリの限界まで行き止まりのルートをカリングします。

(ゴルフ)コード

コードに進みます(ここまたはここで ungolfedバージョンを取得します):

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::sourceroute-to-get-here- ammo。したがって、上記のソリューションでは、次のように読み取ります。

  • ステップ0、前哨基地の1弾薬を持ちます100
  • ステップで1、ルート1を使用して、3弾薬を終了する前post 基地に到達します97
  • ステップで2、ルート1を使用して、1弾薬を終了する前post 基地に到達します95
  • ...

最後に

だから、私は私のソリューションを打ち負かすのを難しくしたことを願っていますが、試してください!私に対してそれを使用し、いくつかの並列処理、より良いグラフ理論などを追加します。私が考えているいくつかのことがこのアプローチを改善する可能性があります。

  • アルゴリズムが進行するにつれて、ループを積極的に「削減」して、不必要なリトレッドをカットします。
    • 例:問題の例では、ループ1-2-3およびその他の順列を「1ステップ」とみなし、より早くサイクルを終了できるようにします。
    • たとえば、ノード1にいる場合は、(a)2に進む、(b)1に進む、(c)1つのステップとして1-2-3に進むなどのいずれかを実行できます。これにより、解決された深さを幅に折り畳むことができ、特定の深さでルートの数が増えますが、長いサイクルの解決までの時間を大幅に短縮できます。
  • 死んだルートを間引く。私の現在のソリューションは、特定のルートが行き止まりであることを「覚えていない」ため、毎回それを再発見する必要があります。死が確実であり、それを超えて進まないルートで最も早い瞬間を追跡する方が良いでしょう。これをやった...
  • 注意すれば、デッドルートカリングをサブルートカリングとして適用できます。たとえば、1-2-3-4が常に死に至り、ソルバーがルート1-3-1-2-3-4をテストしようとしている場合、終了が保証されるため、そのパスの下降を直ちに停止する必要があります。がっかりしました。いくつかの注意深い数学で、キル数を計算することはまだ可能です。
  • 時間とメモリを交換するか、行き止まりのルートを積極的に回避できる他のソリューション。これもやった!

いい答えだ!問題を解決できるのは彼らだけである場合、誰がコードをゴルフする必要がありますか?私は今、独自のソリューションを作成する意欲があるので、それに取り組みます。
レインボルト

すばらしい、これがこれを実現することを願っていました。あなたが役に立つと思う私の答えから何かを借りたり盗んだりしてください!もちろん、私とOP以外の人々が:P
ProgrammerDan

私は脇に追い込まれ、あなたのコードを縮小し始めました。以前に答えがグロテスクだと思っていた場合は、これをチェックしてください:tny.cz/17ef0b3a。まだ進行中の作業。
レインボルト14年

ハハ、あなたは本当に脇道に追い込まれた。これまでのところ、見栄えはいいです(コードゴルフにふさわしい恐ろしいことですか?
プログラマー14年

@Rusherこれまでのところ運はありますか?ルート表現の圧縮技術や、処理済みのルートを(ある時点まで)早送りする方法など、改善のアイデアがいくつかあります。
ProgrammerDan 14

2

ソリューションに関するいくつかの抽象的なメモ

時間があるなら、これをアルゴリズムに変換します...

与えられたグラフに対して、townを含むG接続されたサブグラフが存在しG'ます1。無限のソリューションがある場合、接続されたサブグラフが存在するG''G'含有V町やPパス。

パスPG''ように分割することができる{p}内のすべてのパスの次に、最小のコストを有するパス含まP及びP/{p}(スパニングツリー又はおそらくサイクルを形成する)、他のすべてのパスです。私たちは、それが仮定した場合p、それは2つの町(接続します(同じ町に両端を結ぶ)ループエッジではないv1とのv2)とコストがあるcトラバースから、その後、あなた(生存者)弾薬を可能v1v2する総コストで、バック2c弾薬これは、(の合計の増加のために2によってすべての町で弾薬が増加します2|V|G''-から収集されていますそれらのいくつかv1v2)。

あなたはから旅行する場合v1v2とに戻ってv1(複数m回)、その後の旅行を取るv1エッジに沿っP/{p}以外のすべての町を訪問するv1v2に戻る前にv1、これはとりn(達成するためにパスをどこ|P/{p}| ≤ n ≤ 2|P/{p}|あなたはより多くのパスを通過する必要はありませんので、 2回以上)のコストで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つ追加する小さなこと-コストが1のループエッジを検討する必要があるかもしれません。それらのエッジだけが時間内に到達できる場合、それらのエッジだけが勝利条件を形成するからです。
レインボルト14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.