★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行目の置き方によって、以下のように状態が変化する。
- (X-3,X-1)行目におかれた列が隣接している場合、その間に挿入するとそこの不成立が解消するが、新たに(X-1,X+1)行目におかれた列が隣接し、不成立な状態の総和は変化しない。
- それ以外の不成立な隣接状態の間に挿入すると、不成立な状態が1個減る。
- (X-1)行目におかれた列の両隣に挿入すると、不成立な状態が1個増える。
- ただし、最上位の(X-3,X-1)行目におかれた列が隣接している場合のケースがある場合、片隣だけ。
- それ以外のケースは不成立な状態は増減しない。
あとは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でした。