TopCoder SRM 488 Div 2 Hard SolitaireChess

問題概要

8x8のチェス盤にポーンまたはナイトが合計20個まで置かれた局面が2つある。
最初の局面から駒を動きに従って動かし、2番目の局面を作りたい。ただし、

  • 途中では駒と駒は何枚でも同じマスに重なることができる
  • ポーンは1列目まで動くとプロモーションしてナイトになる

ものとする。
このとき、2番目の局面を作るのに必要な最小手数を求めよ。
不可能な場合は-1と答えよ。

解法

ポーンの置き方はGreedyでよい。(下の列にあるものをなるべく下のポーンにうつす)
余ったポーンは1列目まで進めてプロモーションさせる。


するとナイトの対応のみを考えればよくなる。
駒は途中重なってもよいので、ナイトの到着点n個およびナイトの出発点n個を頂点とする二部グラフの重みマッチングに還元される。


n≦20と小さいので、これはO(n*2^n)のビットDPで解けばよい。

ソースコード

int dx[]={-2,-1,1,2,2,1,-1,-2},dy[]={-1,-2,-2,-1,1,2,2,1};
int d[64][64];
int n,e[20][20],dp[2][1<<20];

int solve()
{
	rep(j,1<<n)dp[0][j]=inf;
	dp[0][0]=0;
	
	int cur=0,next=1;
	
	rep(i,n)
	{
		rep(j,1<<n)dp[next][j]=inf;
		rep(j,1<<n)
		{
			if(dp[cur][j]!=inf)rep(k,n)if(!(j&1<<k)&&e[i][k]!=inf)
			dp[next][j|1<<k]=min(dp[next][j|1<<k],dp[cur][j]+e[i][k]);
		}
		swap(cur,next);
	}
	return dp[cur][(1<<n)-1];
}
class SolitaireChess {
	public:
	int transform(vector <string> board1, vector <string> board2) 
	{
		//ナイトの2マス間の必要移動回数はワーシャルフロイドなどであらかじめ求めておく。
		rep(i,64)rep(j,64)d[i][j]=i==j?0:inf;
		rep(i,64)rep(j,8)
		{
			int y=i/8+dy[j],x=i%8+dx[j];
			if(0<=y&&y<8&&0<=x&&x<8)d[i][y*8+x]=1;
		}
		rep(k,64)rep(i,64)rep(j,64)d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
		
		vi sy,sx,gy,gx; //出発点と到着点の座標のリスト
		rep(i,8)rep(j,8)
		{
			if(board1[i][j]=='N')sy.pb(i),sx.pb(j);
			if(board2[i][j]=='N')gy.pb(i),gx.pb(j);
		}
		
		int ans=0;
		rep(j,8)for(int i=7;i>=0;i--)if(board1[i][j]=='P')
		{
			for(int k=i+1;k<8;k++)if(board2[k][j]=='P')return -1;
			for(int k=i;k>=0;k--)if(board2[k][j]=='P')
			{
				board2[k][j]='.'; ans+=i-k;
				goto NEXT;
			}
			ans+=i;
			sy.pb(0); sx.pb(j); //余ったポーンはプロモーション
			NEXT:;
		}
		
		n=sy.size();
		if(n!=gy.size())return -1;
		
		rep(i,n)rep(j,n)e[i][j]=d[sy[i]*8+sx[i]][gy[j]*8+gx[j]];
		ans+=solve();
		
		return ans<inf?ans:-1;
	}