마지막 스탠드-좀비 호드 처치


25

소개

당신은 섬에 혼자 있습니다. 인류의 나머지는 죽었을 것입니다 ( 아마도 user12345의 코드 버그로 인해 ). 좀비 해적 호드가 당신의 섬에 도착했으며 끝이 없습니다. 이제 풍선 껌을 씹거나 씹을 때가되었습니다.

문제

우리의 종말 시나리오는 한 줄에 두 정수에 의해 설명되어 mn. 귀하의 섬에는 1에서 ~까지 고유 번호가 지정된 전초 기지가 있습니다 m. 다음 n라인은 각각 세 개의 정수, 포함 x, yz공백으로 분리된다. x그리고 y두 전초 기지의 고유 ID가 있고, z그들 사이의 경로에 발생합니다 좀비의 수입니다.

당신이 길을 여행 할 때, 당신은 z탄약 을 잃고 z좀비 를 죽 입니다. 같은 길을 다시 여행하면 안타깝게도 같은 수의 좀비가 발생합니다. 경로를 여행 할 때마다 모든 전초 기지에서 +1 탄약이 생성됩니다. 전초 기지 1에서 100의 탄약으로 시작합니다. 탄약이 해당 경로의 좀비 수보다 큰 경로가 없으면 즉시 사망하고 나머지 탄약은 킬로 전환됩니다. 이것이 당신의 마지막 입장입니다.

주어진 시나리오에서 죽일 수있는 최대 좀비 수를 출력하는 프로그램을 작성하십시오. 무한한 수의 좀비를 죽일 수 있다면 간단히 출력하십시오 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 탄약 인 것 같다 .
Peter Taylor

@PeterTaylor 예제를 양방향으로 만들었 기 때문에 두 의견을 모두 철회해야했습니다 . 이제 다시 시작하겠습니다. 모든 경로는 양방향이며 모든 전 초기는 0으로 시작합니다. 이제 예제가 작동합니다.
Rainbolt

@Rusher : 좋은 예입니다! 그것이 실제로 무한히 지속될 수 있음을 보여주기 위해 45 단계를 걸었습니다. 모든 전초 기지에 접근 할 수 있다고 가정하거나 주 그래프에서 전초 기지가 분리 된 경우를 처리하기를 원하십니까?
Claudiu

1
아아 ... 그래서 A에서 B까지의 각 단계마다, 모든 전초 기지에서 탄약을“생성”하고 방문 할 때까지 보관합니다.
Tobia

답변:


14

Java ( 그로테스크 감소 : 8415 5291 3301)

승인. 기본적으로 아무도 해결책을 제출하지 못했습니다. 며칠 전에 나는이 문제를 해결하기 시작했습니다. . 해당 링크를 따라 GitHub를 통해 진행 상황을 확인하십시오.

편집하다

MT0에 의해 식별 된 수정 된 사이클 체커와 함께 훨씬 더 "골프"된 새로운 솔버 버전. 또한 VM에 사용 가능한 메모리 양을 변경하여 조정할 수있는 빨리 감기 경로를 지원합니다. 최신 BIG 편집 : 몇 가지 다른 작은 인덱스 오류와 조기 최적화가 있음을 깨달았으므로 많은 유형의 승리를 고려하지 못했습니다. 그래서 그것은 조심스럽게 수정되었습니다. 새 버전은 더 작고 아래입니다. 참조 경로의 java -Xmx2GB ZombieHordeMin경우 트릭을 꽤 잘 수행합니다 (경고는 다소 시간이 걸립니다).

멋진 사실

매혹적인 게도, 길이 24에서 많은 해결책이있다, 내 해결사는 것을 제외하고, MT0의 다른 있지만, 원칙적으로 동일한 일을 찾아 시작 에 연결된 다른 전초 기지를 방문하여 1. 매혹적인! 인간의 직관에 완전히 반대하지만 완벽하게 유효합니다.

솔루션 하이라이트

여기 내 것이 있습니다. 그것은 (부분적으로) 골프를 쳤으며 b / c는 지수적이고 거의 무차별 솔버입니다. 나는 IDDFS (iterative deepening depth first search) 알고리즘을 사용하므로 건너 뛰지 않는 훌륭한 일반적인 솔버이므로 OP 질문의 부분을 모두 해결 합니다 .

  • 우승 경로가 발견되면 (무한 좀비) 'x'를 출력합니다.
  • 모든 경로가 사망하면 (좀비 좀비), 죽인 최대 수의 좀비를 출력하십시오.

충분한 힘, 메모리 및 시간을 제공하면 느린지도조차도 그렇게 할 것입니다. 이 솔버를 개선하는 데 더 많은 시간을 보냈으며 더 많은 것을 할 수는 있지만 지금은 조금 나아졌습니다. 또한 최고의 무한 좀비 솔루션에 대한 MT0의 조언을 통합하고 이전 버전에서 찾지 못했던 win-checker에서 몇 가지 조기 최적화를 제거했으며 실제로는 설명 된 MT0과 매우 유사한 솔루션을 찾습니다.

다른 몇 가지 주요 내용 :

  • 언급했듯이 IDDFS를 사용하여 가능한 가장 짧은 당첨 경로를 찾으십시오.
  • 그것은 DFS의 핵심이기 때문에 모든 경로가 영웅의 죽음으로 끝나는지를 발견하고 대부분의 좀비가 사망 한 "최상의"경로를 추적합니다. 영웅을 죽여라!
  • 골프 목적으로 Removed 를 보는 것이 더 재미있게 알고리즘을 계측했습니다 . ungolfed 버전을 보려면 github에 대한 링크 중 하나를 따르십시오.
  • 전체에 걸쳐 많은 의견이 있으므로 내 접근 방식에 따라 자체 솔루션 구축을 위해 다시 구현하거나 어떻게 수행 해야하는지 보여주십시오!
  • 메모리 대응 루트 빨리 감기
    • 사용 가능한 시스템 메모리까지, 사망을 초래하지 않은 "종료 경로"를 추적합니다.
    • 멋진 경로 압축 및 압축 해제 루틴을 사용하면 이전에 방문한 모든 경로를 다시 검색하지 못하도록 IDDFS의 이전 반복 진행 상황이 복원됩니다.
    • 의도적 인 사이드 보너스로 막 다른 길로 작동합니다. 막 다른 경로는 저장되지 않으며 향후 IDDFS 깊이에서는 다시 방문하지 않습니다.

솔버의 역사

  • 나는 한 단계의 미리보기 알고리즘을 시도했지만 매우 간단한 시나리오에서는 작동하지만 궁극적으로는 평평합니다.
  • 그런 다음 2 단계 미리보기 알고리즘을 시도했는데 만족스럽지 못했습니다.
  • 그런 다음 n-step lookahead를 구축하기 시작했습니다.이 접근법이 DFS로 환원 될 수 있음을 인식했지만 DFS는 훨씬 우아합니다.
  • DFS를 구축하는 동안 IDDFS는 (a) 최고의 HERO (죽음) 경로 또는 (b) 첫 우승주기를 보장 할 수있었습니다.
  • 승리주기 검사기를 만드는 것은 쉽지만, 성공적인 검사기에 도달하기 전에 몇 가지 매우 잘못된 반복을 거쳐야했습니다.
  • 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: 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

훌륭합니다. 이것이 제가 바라는 바였습니다. 당신이 유용하다고 생각하는 대답을 빌리거나 훔치십시오! 물론 나 자신 이외의 다른 사람들과 OP가 해결하려고 노력하기를 바랍니다. P
ProgrammerDan

부수적 인 코드를 줄이기 시작했습니다. 이전에 답변이 괴상하다고 생각되면 tny.cz/17ef0b3a를 확인하십시오 . 여전히 진행중인 작업입니다.
Rainbolt

하하, 당신은 정말 추억을 얻었습니다. 지금까지는 좋아 보인다 (코드 골프에 대해 적절하게 끔찍한가? 당신은 내가 무슨 뜻인지 알고있다)!
ProgrammerDan

@Rusher 지금까지 행운이 있습니까? 나는 경로 표현 압축 기술과 이미 처리 된 경로를 통해 빨리 진행하는 방법을 포함하여 양조하고있는 개선에 대한 몇 가지 아이디어를 얻었습니다.
ProgrammerDan

2

솔루션에 대한 몇 가지 추상 노트

시간이되면 이것을 알고리즘으로 변환 할 것입니다 ...

주어진 그래프에 대해 town을 포함 G하는 연결된 하위 그래프 G'가 있습니다 1. 무한 솔루션이 경우, 연결된 서브 그래프가 존재할 G''G'포함하는 V도시 및 P경로.

경로 P는 모든 경로의 비용이 최소 이고 다른 모든 경로 (스패닝 트리 또는 사이클을 형성 함) 인 경로 G''{p}포함 하도록 분할 될 수 있습니다 . 우리는이 가정하면 다음 두 도시 (연결합니다 (같은 마을 양쪽 끝을 연결) 루핑 가장자리하지 않습니다 와 )과 비용이 트래버스에서 다음 다음 (생존자) 탄약을 할 수 있습니다 에 대한 총 비용으로 다시 탄약 이것은 (총 증가 2에 의해 모든 도시에서 탄약을 증가 내 - 일부에서 수집 한 것 과 ).PP/{p}pv1v2cv1v22c2|V|G''v1v2

당신은에서 여행하는 경우 v1v2와 다시 v1(여러 m번) 다음에서 여행을 v1가장자리를 따라 P/{p}이외의 모든 도시 방문 v1v2에 반환하기 전에를 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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.