【問題描述】在n*n(1<=n<=10)的棋盤上放k(0<=k<=n*n)個國王(可攻擊相鄰的8 個格子),求使它們沒法互相攻擊的方案總數。算法
【輸入格式】輸入有多組方案,每組數據只有一行爲兩個整數n和k。數組
【輸出格式】每組數據一行爲方案總數,若不可以放置則輸出0。數據結構
【問題分析】spa
由問題很容易聯想起經典的「八皇后」問題,彷佛就是「皇后」變成了「國王」,並且格子範圍彷佛也差很少,所求問題也同樣。那麼這個問題也能用搜索解決嗎?.net
可稍加分析可知搜索是很難勝任的,由於國王的數目能夠是很大,加上它與「八皇后」問題的一個本質上的不一樣即是每一個國王隻影響周圍的一個格子,因此剪枝條件也不多,指數級別的搜索是沒法在時限內出解的。code
那麼通常的動態規劃能解決嗎?典型的二維DP,F[I,J]彷佛沒法很好地把狀態表示出來,所以咱們只能考慮狀態壓縮的動態規劃。blog
首先咱們要注意到這題的關鍵——每一個國王隻影響周圍八個方向的一個格子,它雖然否認了搜索,卻給狀態壓縮帶來了無限生機!get
咱們改變以前動態規劃的思惟方式,一行一行地擺放國王,當咱們擺放第I行時,這一行只會和先後一行的互相影響,而這一行的狀態是能夠由咱們肯定的。那是否能夠把一行看成一個總體,而後像傳統的動態規劃那樣進行處理呢?讓咱們試一下。string
每一行它對下一行的影響就體如今這一行的擺放方式以及以前總共放了多少個國王。因此咱們能夠把擺放方式做爲狀態,設f[i,j,s]表示第i行狀態爲a[j]且前i行已放s個國王的方案總數。這樣很容易便獲得了一個粗略的方程:
F[i,j,S]=∑F[i-1,k,T]
a[j],a[k]分別表示一種擺放方式,F[i,j]表示第i行用a[j]的擺放方式,且a[j]與a[k]相兼容。而且S等於T加上a[j]這種方式在這一行放置的國王數。
很明顯這個方程是沒有後效性的,可關鍵就在於j,k怎麼在計算機上表示出來,這就須要咱們的主題:狀態壓縮。
看圖便知,每個格子只有兩種狀態,放和不放,而且注意到格子寬度最大爲9。由這便想到了熟悉的二進制表示法。每個格子對應一個二進制位。這樣每一行便對應一個N位的二進制數,以下圖所示:
1 0 0 0 1 0 0 0
即十進制的128+8=136
這樣咱們就能夠把一種擺放方式轉化爲一個數,這樣上面方程中的J,K就能夠用數字來代替。咱們就把一行的擺放方式做爲狀態,並把它壓縮成了一個數!具體的算法流程以下:
①對於每一行,咱們經過搜索得出一個合法狀態。
②而後再枚舉上一行與這一行相容的狀態再累加便可,狀態用N位的二進制數表示,最大僅爲512,因此一個512*9*81的數組就能夠了,還能夠用滾動數組的技巧。空間是確定能夠承受的。
而一個粗略的時間複雜度:O(K*N*2^N*2^N),彷佛大了點。不過注意到因爲國王是不能相鄰放置的。因此咱們能夠用一個f(n)來表示當列數爲n時每一行可能的放置總數。則f(n)=f(n-1)+f(n-2)初始值:f(1)=2,f(2)=3。
則f(9)=89。所以最大也纔是9*89*89*81≈6000000。是能夠承受的。
壓縮行,有king是1,沒有是0.
判斷可行,就是將上一行與這一行按位與,接着左移,右移。
剩下的就是簡單的dp了。
#include<iostream> #include<cstring> #include<cstdio> using namespace std; const int N=10; int n,k,a[10005],b[10005],tp; long long f[N][N*N][1<<N],ans; //第i行,一共擺放了j個king,第i行的擺放狀況是g void dfs(int num,int lst,int at,int bt) { if(num==n) { ++tp; a[tp]=at; b[tp]=bt; return ; } dfs(num+1,lst,at,bt); if(num-lst>=2) { at|=(1<<num); bt++; dfs(num+1,num,at,bt); } } int main() { scanf("%d%d",&n,&k); dfs(0,-2,0,0); f[0][0][0]=1; int end=(1<<n)-1; for(int i=1;i<=n;i++) for(int j=0;j<=k;j++) for(int g=0;g<=end;g++) if(f[i-1][j][g]>0) for(int h=1;h<=tp;h++) if((a[h]&g)==0&&(a[h]&(g<<1))==0&&(a[h]&(g>>1))==0&&j+b[h]<=k) f[i][j+b[h]][a[h]]+=f[i-1][j][g]; for(int i=0;i<=end;i++) ans+=f[n][k][i]; printf("%lld\n",ans); return 0; }