Cruel Bingo (AOJ2231)
問題:N*Nのビンゴ(ななめあり(対角線2つ))の盤面のうち,N*(N-1)個開いてるのにビンゴができてないCruelな盤面は何個あるか,ただし開いてることが確定してるマスがK個与えられる.N<=32,K<=8
知識0,頭5,ライブラリ0,実装1,英語0
気づけば一瞬ですね・・・
どのNマスを開かないマスにするか,を決めるとそいつらがタテ・ヨコ・ナナメ全てを妨害してればいいと言い換えられる.
まず上から埋めていくただのbitDPを思いつく.簡単.
つぎにななめがないなら,どこを埋めたか のうちK個以外はどこが埋まってても一緒なので32*2^8みたいなのができることがわかる.
しかしななめがあるとこの方法ではうまくやるのが難しい.
ななめに関して考えなくて済むような埋め方はないか?→真ん中から埋めれば良いのでは
きれいに1個ずつ埋めるのは不可能になるので,dp[i][j][k][l]=真ん中の(2i(+1))*(2i(+1))まででj個うめて,ななめが既に埋められているかどうか(k,l) を持つと更新できる.
K個に関しては,このまま扱うと面倒なので包除にすると楽に扱える(この行,列はもうおいちゃだめ というのを持ってそこにおかないようにするだけで済むのでDPで覚えなくて済む).
更新は新しく増えた周をななめの点4つと辺4つに分解して,それらを埋めるかどうか2^8をまわれば(実際これは無駄が多いので,事前に計算すると34状態くらいしかない)楽にできる.
計算量は 2^K * N * N/2 * 2 * 2 * 34 * 8 みたいなのになった.
まあ真ん中から埋めるということに気づきさえすれば普通のDPだし解いてみると1200+という気はしないなあ でも発想が面白くて好き.
#include <bits/stdc++.h> #define rep(i,n) for(int i=0;i<(int)(n);i++) #define rep1(i,n) for(int i=1;i<=(int)(n);i++) #define all(c) c.begin(),c.end() #define pb push_back #define fs first #define sc second #define show(x) cout << #x << " = " << x << endl #define chmin(x,y) x=min(x,y) #define chmax(x,y) x=max(x,y) using namespace std; int N,mod=10007; vector<int> _bs; inline int P(int x,int y){ if(y==0) return 1; if(y==1) return x; if(y==2) return x*(x-1); } inline void add(int &x,int y){ x+=y; x%=mod; } int solve(vector<int> x,vector<int> y){ int K=x.size(); int dp[17][33][2][2]={}; // ,put,sl,bsl bool banx[32]={},bany[32]={}; bool sl=0,bsl=0; rep(i,K){ if(banx[x[i]]||bany[y[i]]) return 0; banx[x[i]]=bany[y[i]]=1; if(x[i]==y[i]) bsl=1; if(x[i]+y[i]==N-1) sl=1; } dp[0][0][sl][bsl]=1; int L=-N%2,M=(N+1)/2; int A=(N-1)/2,B=N/2; rep(i,M){//it int X=0,Y=0; for(int j=A+1;j<=B-1;j++){ if(!banx[j]) X++; if(!bany[j]) Y++; } rep(j,N+1-K){//put rep(k,2) rep(l,2){//sl,bsl if(dp[i][j][k][l]==0) continue; if(L==-1){ add(dp[i+1][j][k][l],dp[i][j][k][l]); if(!banx[A]&&!bany[A]&&j+1<=N-K) add(dp[i+1][j+1][1][1],dp[i][j][k][l]); }else{ for(int b:_bs){ bool bs[8]={}; int C=0; rep(a,8) bs[a]=(b>>a)&1; rep(a,8) if(bs[a]) C++; if(L==0&&(bs[4]||bs[5]||bs[6]||bs[7])) continue; if(banx[A]&&(bs[0]||bs[4]||bs[1])) continue; if(banx[B]&&(bs[2]||bs[6]||bs[3])) continue; if(bany[A]&&(bs[3]||bs[7]||bs[0])) continue; if(bany[B]&&(bs[1]||bs[5]||bs[2])) continue; int ad=P(Y-j,bs[4]+bs[6])*P(X-j,bs[5]+bs[7])%mod*dp[i][j][k][l]; if(ad) add(dp[i+1][j+C][k|bs[1]||bs[3]][l|bs[0]|bs[2]],ad); } } } } A--,B++,L+=2; } /* rep(i,M+1) rep(j,N+1-K) rep(k,2) rep(l,2){ printf("dp[%d][%d][%d][%d]=%d\n",i,j,k,l,dp[i][j][k][l]); } puts("");*/ return dp[M][N-K][1][1]; } int xx[8],yy[8]; int main(){ rep(i,1<<8){ bool b[8]={}; rep(j,8) b[j]=(i>>j)&1; bool ok=1; if(b[0]+b[4]+b[1]>1) ok=0; if(b[1]+b[5]+b[2]>1) ok=0; if(b[2]+b[6]+b[3]>1) ok=0; if(b[3]+b[7]+b[0]>1) ok=0; if(ok) _bs.pb(i); } int K; cin>>N>>K; rep(i,K) cin>>xx[i]>>yy[i]; int ans=0; rep(i,1<<K){ vector<int> x,y; rep(j,K) if((i>>j)&1) x.pb(xx[j]),y.pb(yy[j]); int t=1; if(x.size()%2) t=-1; int tmp=solve(x,y)*t; // show(i); // show(tmp); add(ans,tmp); } cout<<(ans+mod)%mod<<endl; }