(HDU - 2553)N(八)皇后問題更快速的解法——用二進制優化搜索常數

題目鏈接:

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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章