周りの解法と違うけど、自分は結構この解き方好き。
http://code-festival-2014-china-open.contest.atcoder.jp/tasks/code_festival_china_d
問題
大きさH*Wの2次元グリッドがある。
一部のセルは移動不可能である。
グリッド上のあるセルSから、別のセルA及びBまで隣接セルを辿ってそれぞれ移動したい。
その際、以下の条件を守りたい。
- S→A及びS→Bにたどるルートは(始点のSを除き)同一セルを含んではならない。
- S→A及びS→Bにたどるルートはそれぞれ最短経路でなければならない。
条件を満たすS→A、S→Bの経路は存在するか。するならその例を示せ。
解法
他の人の解法は、まず最短経路を求めたうえで流量2のフローを流し、経路をバックトラックするものだった。
想定解かわからないけど自分は他の解法を取ったので紹介。
まず、A,B,Sから各セルへの最短距離をBFSなりダイクストラなどで求める。
次にSからA及びSからBの経路を以下のように求める。
- 状態は2つのセルの位置の対で示す。初期状態は(S,S)で、これを(A,B)に移動させたい。
- まず前者を現在位置の隣接セルのどこかに移動させる。その際、Aまでの距離が1縮まる方向にしか移動できない。
- つぎに後者を現在位置の隣接セルのどこかに移動させる。その際、Bまでの距離が1縮まる方向にしか移動できない。また、前者と同一セルには移動できない。
- それぞれ目的地のA,Bにたどりつくまで、交互に1マスずつ進めていく。
幸いグリッドサイズH*Wは小さいので、状態として2つのセルの位置を取っても計算が間に合う。
int H,W; string S[51]; int D[3][51][51]; int X[3],Y[3]; int memo[51][51][51][51]; int okok(int x1,int y1,int x2,int y2) { if(D[1][y1][x1]==0&&D[2][y2][x2]==0) return 0; int& ret= memo[x1][y1][x2][y2]; int i; if(ret!=-2) return ret; ret = -1; int turn=(D[0][y1][x1]!=D[0][y2][x2]); if(D[1][y1][x1]==0) turn=1; if(D[2][y2][x2]==0) turn=0; FOR(i,4) { int dd[4]={0,1,0,-1}; int tx,ty; if(turn==0) { tx=x1+dd[i]; ty=y1+dd[i^1]; if(tx<0|| ty<0||tx>=W||ty>=H) continue; if(tx==x2&&ty==y2) continue; if(D[1][ty][tx]!=D[1][y1][x1]-1) continue; if(okok(tx,ty,x2,y2)>=0) return ret=i; } else { tx=x2+dd[i]; ty=y2+dd[i^1]; if(tx<0|| ty<0||tx>=W||ty>=H) continue; if(tx==x1&&ty==y1) continue; if(D[2][ty][tx]!=D[2][y2][x2]-1) continue; if(okok(x1,y1,tx,ty)>=0) return ret=i; } } return ret; } void fix(int x1,int y1,int x2,int y2) { int turn=0; int& ret= memo[x1][y1][x2][y2]; if(D[1][y1][x1]==0&&D[2][y2][x2]==0) return; turn=(D[0][y1][x1]!=D[0][y2][x2]); if(D[1][y1][x1]==0) turn=1; if(D[2][y2][x2]==0) turn=0; int dd[4]={0,1,0,-1}; if(turn==0 && S[y1][x1]=='.') S[y1][x1]='a'; if(turn==1 && S[y2][x2]=='.') S[y2][x2]='b'; if(turn==0) fix(x1+dd[ret],y1+dd[ret^1],x2,y2); if(turn==1) fix(x1,y1,x2+dd[ret],y2+dd[ret^1]); } void solve() { int i,j,k,l,r,x,y; string s; cin>>H>>W; memset(D,0x3f,sizeof(D)); queue<int> Q; FOR(y,H) { cin>>S[y]; FOR(x,W) { if(S[y][x]=='S') X[0]=x, Y[0]=y, D[0][y][x]=0, Q.push(0+y*100+x); if(S[y][x]=='A') X[1]=x, Y[1]=y, D[1][y][x]=0, Q.push(10000+y*100+x); if(S[y][x]=='B') X[2]=x, Y[2]=y, D[2][y][x]=0, Q.push(20000+y*100+x); } } while(Q.size()) { int k=Q.front(); Q.pop(); int st=k/10000, cy=k/100%100, cx=k%100; FOR(i,4) { int dd[4]={0,1,0,-1}; int tx=cx+dd[i],ty=cy+dd[i^1]; if(tx<0 || ty<0 || tx>=W || ty>=H) continue; if(S[ty][tx]=='#') continue; if(D[st][ty][tx]<=D[st][cy][cx]+1) continue; D[st][ty][tx]=D[st][cy][cx]+1; Q.push(st*10000+ty*100+tx); } } FOR(y,51) FOR(x,51) FOR(i,51) FOR(j,51) memo[y][x][i][j]=-2; if(okok(X[0],Y[0],X[0],Y[0])<0) { cout<<"NA"<<endl; } else { fix(X[0],Y[0],X[0],Y[0]); FOR(y,H) cout<<S[y]<<endl; } }
まとめ
結構面白い解き方だと思ったのだけど、周りがフローばっかりでちょっと残念。