洛谷傳送門數組
在N×N的棋盤裏面放K個國王,使他們互不攻擊,共有多少種擺放方案。國王能攻擊到它上下左右,以及左上左下右上右下八個方向上附近的各一個格子,共8個格子。spa
注:數據有增強(2018/4/25)code
只有一行,包含兩個數N,K ( 1 <=N <=9, 0 <= K <= N * N)get
所得的方案數io
輸入 #1複製class
輸出 #1複製變量
原諒我一開始看到題還覺得是爆搜。。。搜索
實際上是一道狀態壓縮的題目。二進制
蒟蒻本身一個比較大的進步就是把本身狀態設置對了...統計
設置:\(dp[i][j][k]\)爲第\(i\)行狀態爲\(j\)、已經用了\(k\)個國王時的方案數。
狀態壓縮大致有這麼幾步:設置狀態\(\rightarrow\)考慮轉移方式\(\rightarrow\)按轉移方式考慮預處理和判斷轉移條件\(\rightarrow\)開始轉移\(\rightarrow\)統計答案。
那麼咱們設置好狀態,開始考慮轉移方式:咱們發現,如果想從第\(i-1\)行開始轉移,轉移的條件一是當前和上一次的狀態,可是,這些狀態的改變必然還會改變國王的個數。也就是說,這數組的兩維是有聯繫的,是自變量和因變量的關係。因此咱們所以想到,既然是自變量和因變量的關係,咱們莫不如由此構建一個映射,存下來每一個狀態和每一個狀態須要的國王人數。這樣咱們轉移的時候就沒啥問題了。
如何預處理呢?咱們想到,咱們須要按行處理狀態,每一個狀態有放國王和不放國王兩種選擇。由於是預處理,咱們是確定不能用遞推和\(DP\)的(你想幹啥)
因此咱們考慮搜索。
一次搜索能夠處理出全部合法的行的方式。
這裏插一嘴,由於咱們已經把全部合法的行的方式都求出來了,因此咱們不必再把\(dp\)數組的第二維開那麼大,構建好映射關係以後,直接用\(cnt\)代替這個二進制狀態便可。(由於\(1-cnt\)的每一個數都對應着一個數組\(s[i]\)做爲狀態。)
而後再轉移的時候進行判斷是否合法就能夠。
轉移方程:
\[ dp[i][j][l]+=dp[i-1][k][l-num[j]] \]
這裏的\(k,j\)分別表示一種狀態。
代碼:
#include<cstdio> #define int long long using namespace std; int n,K,cnt,ans; int s[100],num[100]; int dp[10][100][110]; //dp[i][j][k]表示前i-1行放完,第i行狀態爲j、有k個國王時的方案數 //狀態0/1:0:國王攻擊不到;1:被國王佔領 void dfs(int pos,int st,int tot) { if(pos>=n) { s[++cnt]=st; num[cnt]=tot; return; } dfs(pos+1,st,tot); dfs(pos+2,st+(1<<pos),tot+1); } signed main() { scanf("%lld%lld",&n,&K); dfs(0,0,0); for(int i=1;i<=cnt;i++) dp[1][i][num[i]]=1; for(int i=2;i<=n;i++) for(int j=1;j<=cnt;j++) for(int k=1;k<=cnt;k++) { if(s[j]&s[k]) continue; else if(s[j]&(s[k]>>1)) continue; else if(s[j]&(s[k]<<1)) continue; else for(int l=num[j];l<=K;l++) dp[i][j][l]+=dp[i-1][k][l-num[j]]; } ans=0; for(int i=0;i<=cnt;i++) ans+=dp[n][i][K]; printf("%lld",ans); return 0; }