在n×n格的棋盤上放置彼此不受攻擊的n個皇后。按照國際象棋的規則,皇后可以攻擊與之處在同一行或同一列或同一斜線上的棋子。n后問題等價於在n×n格的棋盤上放置n個皇后,任何2個皇后不放在同一行或同一列或同一斜線上。
Input
輸入的第一個為測試樣例的個數T,接下來有T個測試樣例。每個測試樣例的只有一行一個數n ( n < 15 ),表示棋盤的大小。
Output
對應每個測試樣例輸出一行結果:可行方案數。
Sample Input
2 3 4
Sample Output
0 2
記錄一下用BFS來解皇后問題
問題分析:
不同於DFS,BFS主要是用隊列來實現,遍歷行,在當前棋盤狀態下將下一行所有可能性入隊。
這就需要在每一個節點存儲當前棋盤狀態
無優化代碼
#include<iostream> #include<queue> using namespace std; #define MAX_DATA 15 struct point { int x; int y; int m[MAX_DATA][MAX_DATA]; int length; }; int n; queue<point> q; int ans; void init(point& p) { for(int i=0;i<n;++i){ for(int j=0;j<n;++j){ p.m[i][j]=0; } } } void doFlag(point& p) { for(int i=0;i<n;++i){ p.m[p.x][i]=1; p.m[i][p.y]=1; } int i=1,x1=p.x,y1=p.y; while(x1-i>=0||y1-i>=0||x1+i<n||y1+i<n){ if (x1-i>=0&&y1-i>=0){ p.m[x1-i][y1-i]=1; } if (x1-i>=0&&y1+i<n){ p.m[x1-i][y1+i]=1; } if (x1+i<n&&y1-i>=0){ p.m[x1+i][y1-i]=1; } if (x1+i<n&&y1+i<n){ p.m[x1+i][y1+i]=1; } i++; } } void bfs() { for(int i=0;i<n;++i){ point p; p.x=0; p.y=i; p.length=1; init(p); doFlag(p); q.push(p); } while(q.size()){ point fp=q.front(); int len=fp.length; if(len==n){ ans++; } q.pop(); for(int i=0;i<n&&len!=n;++i){ if(!fp.m[len][i]){ point p=fp; p.x++; p.y=i; p.length++; doFlag(p); q.push(p); } } } } int main() { int T; cin>>T; while(T--){ cin>>n; ans=0; bfs(); cout<<ans<<endl; } return 0; }
但是這樣子實現太費空間了
當n==14時,會報錯提示已占用內存過大
queue的大小時比較難簡化了,就簡化節點試試
第一步優化代碼
#include<iostream> #include<queue> using namespace std; #define MAX_DATA 15 struct point { int m[MAX_DATA];///m[i]表示第i行m[i]有皇后 int length; }; queue<point>que; int n; int ans; bool ok(point nowsta,int len) { for(int i=1;i<len;i++){ if(abs(nowsta.m[len]-nowsta.m[i])==abs(i-len)|| nowsta.m[len] == nowsta.m[i]) return false; } return true; } void bfs() { for(int i=1;i<=n;i++){///初始n個點加進去 point p; for(int j=1;j<=n;j++) p.m[j]=0; p.m[1]=i; p.length=2; que.push(p); } while(!que.empty()){ point p=que.front(); que.pop(); if(p.length>n){ ans++; continue; } int nowrow=p.length; p.length++; for(int i=1;i<=n;i++){///枚舉列 p.m[nowrow]=i; if(ok(p,p.length-1)) que.push(p); } } } int main(){ int T; cin>>T; while(T--){ ans=0; while(!que.empty()){ que.pop(); } cin>>n; bfs(); cout<<ans<<endl; } return 0; }
用一維數組來存儲棋盤狀態,簡化到原先1/15的空間
這樣就在n==14時就可以跑出結果了,可以看到隊列最大的時候有六百多萬個節點
不過提交還是MLE,節點還得進一步簡化
ac代碼
#include<iostream> using namespace std; #include<queue> int n; int ans; struct node { int step; int left; int right; int up; }; queue<node> q; void bfs() { node h; h.step=0; h.left=0; h.right=0; h.up=0; q.push(h); while(!q.empty()){ node t=q.front(); q.pop(); int left=t.left; int right=t.right; int up=t.up; left>>=1;//棋盤上左移一列 right<<=1; for(int i=1;i<=n;i++){ int now=(1<<(i-1));//當前列 if(!(now&left)&&!(now&right)&&!(now&up)){ h.step=t.step+1; h.left=(now|left); h.right=(now|right); h.up=(now|up); if(h.step==n){ ans++; } else{ q.push(h); } } } } } int main() { int T; cin>>T; while(T--){ cin>>n; ans=0; while(!q.empty()){ q.pop(); } bfs(); if(n==1){ ans+=1; } cout<<ans<<endl; } return 0; }
代碼分析
這里每個節點只有4個整形變量,空間上可以符合要求
step代表當前行
使用二進制來標記棋盤
int now=(1<<(i-1));
這里的now表示當前列,1、2、3...列分別用2^0、2^1、2^2...來表示
對應的
left>>=1; right<<=1;
也分別表示在棋盤上左移一列和右移一列
而判斷條件
!(now&left)&&!(now&right)&&!(now&up)
now&left、now&right、now&up全為0,結合上面left和right的操作可知,是判斷當前列的左上方、正上方、右上方是否都沒有皇后,若有,則條件不成立
若條件成立,則在當前位置放置皇后,即
h.step=t.step+1; h.left=(now|left); h.right=(now|right); h.up=(now|up);
left>>=1; right<<=1;