題目鏈接:
http://acm.hdu.edu.cn/showproblem.php?pid=2553
題目:
在N*N的方格棋盤放置了N個皇后,使得它們不相互攻擊(即任意2個皇后不允許處在同一排,同一列,也不允許處在與棋盤邊框成45角的斜線上。
你的任務是,對於給定的N,求出有多少種合法的放置方法。
Input
共有若干行,每行一個正整數N≤10,表示棋盤和皇后的數量;如果N=0,表示結束。
Output
共有若干行,每行一個正整數,表示對應輸入行的皇后的不同放置數量。
Sample Input
1
8
5
0
Sample Output
1
92
10
題目分析:
首先,這個題是深度優先搜索的入門題,也是在算法課上一定會說到的經典問題。那麼,在這兒我們就不去考慮最基礎的解法,而是去找到一種儘可能快的解法。
那麼,我們就需要知道時間到是浪費在哪?這個題搜索解法的實質其實就是在試圖枚舉所有的可能的擺放狀態。既然枚舉是必不可少的,那麼時間的浪費無疑就是兩個方面。第一個方面就是當前狀態已經不合法,卻仍然向下搜索。第二個方面就是檢測當前狀態是否合法所浪費的時間。
第一個方面的問題很好解決,如果當前狀態不合法,直接返回即可。(估計絕大多數人都已經這麼做了吧,哈哈)
第二個方面的問題就比較複雜,檢測當前狀態是否合法,最樸素的方法就是從當前行向上逐個遍歷每一行,來判斷這一列是否放過皇后,同理也可以來判斷斜線的情況。稍微優化一點的方法就是開三個數組來記錄狀態,第一個數組記錄第 i 列是否放過皇后,另外兩個分別用來記錄從左上到右下、從右上到左下的斜線的放置情況,座標與列號的變換方程可以自行推導得到(如下)。
數字相同的爲同一斜線,那麼其實就是到左上角的點和右上角的點的距離,怎麼算就不用在贅述了吧。
在第二種方法的基礎上,我們能不能更優化一點呢?
衆所周知,數的二進制形式可以用來表示狀態。比如,在N皇后問題中,我們也可以用1來代表可以放,0代表不可以放。那麼,我們就可以用一個二進制數來存儲每一列是否可以放置皇后,而不用再使用一個數組了,同理,我們也可以用兩個二進制數來分別表示當前所在左斜線(左上到右下),和當前所在右斜線(右上到左下)是否可以放置。
比如,現在有三個數(用col代表每一列的狀態,ld代表左斜線的狀態,rd代表右斜線)。
col = 1111,ld = 1111, rd = 1111;(顯然是求解4皇后問題)
那麼,我在第一行的第一個位置放置了一個皇后,那麼對於第二行來說,狀態就變成了
col = 0111, ld = 1111, rd = 1011;
然後,在第二行的第三個位置放置一個皇后,那麼對於第三行來說,狀態就變成了
col = 0101, ld = 1011, rd = 1100;
通過上面的例子,大概應該可以明白是怎麼來用二進制來表示狀態的了吧。
那麼,對於當前的狀態,我有哪幾個位置可以放置皇后呢?
比如col = 0111, ld = 1111, rd = 1011;
那麼,只有第三個位置,第四個位置可以放,實際上就是把這三個數按位與之後得到的數的二進制位爲1的位置(原因就是按位與是當前二進制位全爲1纔是1,正好對應我們所說的,當前列,左斜線,右斜線都可以放,纔可以放。)
col & ld & rd = 0011;(第三個、第四個位置爲1)
然後,col、ld 和 rd 是怎麼變化的呢?
col當然簡單,放置了哪一列,直接把那一列對應的二進制位變成0即可,ld和rd呢?通過觀察我們發現,對ld來說,就是先把對應二進制位變成0,然後左移一位(補1)即可。對rd來說,就是先把對應二進制位變成0,然後右移一位(補1)即可。(原因很顯然哈)這樣我們就得到了狀態之間的變化的關係,就可以更快的來完成狀態的檢測,同時,對於不可行的狀態,我們也不需要進行額外的嘗試,所以可以這樣優化掉很大的常數。(也是一種很重要的思維方式)。
同時這個題要打表,否則就會超時0.0
代碼:(弄懂了思想,代碼就很簡單了)
#include <algorithm>
#include <cstdio>
#include <iostream>
using namespace std;
int sum = 0, ans[15], n;
void dfs(int row, int ld, int rd) {
if (!row) sum++;
int val = row & ld & rd, pos;
while (val) {
pos = (val & -val);
val -= pos;
dfs(row ^ pos, ((ld ^ pos) << 1) | 1,
((rd ^ pos) >> 1) | (1 << (n - 1)));
}
}
void init() {
int temp;
for (n = 1; n <= 10; n++) {
sum = 0;
temp = (1 << n) - 1;
dfs(temp, temp, temp);
ans[n] = sum;
}
}
int main() {
init();
while (scanf("%d", &n) != EOF && n) {
printf("%d\n", ans[n]);
}
return 0;
}