kmjp's blog

競技プログラミング参加記です

yukicoder : No.93 ペガサス

★3つ…?
http://yukicoder.me/problems/101

問題

Nクイーンのアレンジ問題。
将棋の飛車と桂馬を合わせた動きができる飛馬という駒を考える。
N*Nの盤面にこの駒を同じ向きでN個、互いに取られないように置く置き方の数を答えよ。

解法

writer解説を見て回答。
(X-1)*(X-1)の盤面の状態に対し、最下段X行目のどこかの列に1か所駒を足し、X*Xの盤面を作る挿入DPで処理する。

各列にはどこかの行に1個駒がある。
ここで、隣接する列において駒がある行の差が2の場合、不成立な隣接状態があると考える。
X行目まで処理した状態として、dp[X][不成立な列の対の数][(X-3,X-1)行目におかれた列は隣接しているか][(X-2,X)行目におかれた列は隣接しているか]を持ち、(X+1)行目の状態を更新する。

X+1行目の置き方によって、以下のように状態が変化する。

  1. (X-3,X-1)行目におかれた列が隣接している場合、その間に挿入するとそこの不成立が解消するが、新たに(X-1,X+1)行目におかれた列が隣接し、不成立な状態の総和は変化しない。
  2. それ以外の不成立な隣接状態の間に挿入すると、不成立な状態が1個減る。
  3. (X-1)行目におかれた列の両隣に挿入すると、不成立な状態が1個増える。
    1. ただし、最上位の(X-3,X-1)行目におかれた列が隣接している場合のケースがある場合、片隣だけ。
  4. それ以外のケースは不成立な状態は増減しない。

あとはDPを処理してdp[N][0][0][0]が答え。

ll N;
ll mo=1000000007;
ll& add(ll& a,ll b) { return a=(a+b)%mo;}
ll dp[2][1005][2][2];

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>N;
	if(N==1) return _P("1\n");
	if(N==2) return _P("2\n");
	
	dp[0][0][0][0]=2;
	for(i=2;i<N;i++) {
		int cur=i%2,tar=cur^1;
		ZERO(dp[tar]);
		FOR(x,i-1) {
			ll v;
			
			if(x>=2) {
				v=dp[cur][x][1][1];
				if(v) {
					add(dp[tar][x][1][1]  ,v);             // split (i-1,i-3)
					add(dp[tar][x-1][0][0],v);             // split (i,i-2)
					add(dp[tar][x-1][1][0],v*(x-2));       // split others
					add(dp[tar][x+1][1][1],v);             // near i-1
					add(dp[tar][x][1][0]  ,v*(i+1-(x+1))); // else
				}
			}
			if(x>=1) {
				v=dp[cur][x][0][1];
				if(v) {
					add(dp[tar][x-1][0][0],v);           // split (i,i-2)
					add(dp[tar][x-1][1][0],v*(x-1));     // split others
					add(dp[tar][x+1][1][1],v*2);         // near i-1
					add(dp[tar][x][1][0]  ,v*(i+1-x-2)); // else
				}
				v=dp[cur][x][1][0];
				if(v) {
					add(dp[tar][x][0][1],  v);           // split (i-1,i-3)
					add(dp[tar][x-1][0][0],v*(x-1));     // split others
					add(dp[tar][x+1][0][1],v);           // near i-1
					add(dp[tar][x][0][0],  v*(i+1-x-1)); // else
				}
			}
			
			v=dp[cur][x][0][0];
			if(v) {
				add(dp[tar][x+1][0][1]        ,2*v);         // near i-1
				if(x>0) add(dp[tar][x-1][0][0],x*v);         // split
				add(dp[tar][x][0][0]          ,(i+1-2-x)*v); // else
			}
		}
	}
	
	cout<<dp[N%2][0][0][0]<<endl;
}

まとめ

状態遷移が面白い挿入DPでした。