給定一個 n*nn∗n 的棋盤,棋盤中有一些位置不能放皇后。現在要向棋盤中放入 nn 個黑皇后和 nn個白皇后,使任意的兩個黑皇后都不在同一行、同一列或同一條斜線(包括正負斜線)上,任意的兩個白皇后都不在同一行、同一列或同一條斜線(包括正負斜線)上。問總共有多少種放法?nn 小於等於 88。
輸入格式
輸入的第一行為一個整數 nn,表示棋盤的大小。
接下來 nn 行,每行 nn 個 00 或 11 的整數,如果一個整數為11,表示對應的位置可以放皇后,如果一個整數為 00,表示對應的位置不可以放皇后。
輸出格式
輸出一個整數,表示總共有多少種放法。
樣例輸入1
4 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
樣例輸出1
2
樣例輸入2
4 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1
樣例輸出2
0
要想理解2n皇后的做法就需要我們先理解n皇后問題。
這里貼上百度的解釋:https://baike.baidu.com/item/%E5%85%AB%E7%9A%87%E5%90%8E%E9%97%AE%E9%A2%98/11053477?fr=aladdin
n皇后問題其實不是很難。它用到思想是算法中的回溯思想。
如果我想找到所有的n皇后的有效解個數,那么我們應該從第一層開始。在這里我准備邊講代碼邊分析、
開始的時候我們先定義三個全局的數組變量
int num[8][8];
int location[8];
int maxn=-1;
這三個變量分別表示為 不同位置的權值(因為這道8皇后問題是為了找到所有情況中權值和最大的那個情況)、這八行數的各行的位置記錄、所有情況中的最大值是多少。
這里為什么要定義全局變量呢?我下面會講到。
之后就要看主函數了,
int main(){ int k; cin>>k; while(k--){ for(int i=0;i<8;i++){ for(int h=0;h<8;h++){ cin>>num[i][h]; } } Queen(0); cout<<maxn<<endl; } }
主函數很簡單,就是循環賦值然后調用Queen回溯,然后得出來最大值maxn並輸出。
下面是關鍵代碼:
int valid(int rows,int columns){ for(int i=0;i<rows;i++){ if(columns==location[i]||abs(rows-i)==abs(columns-location[i])) return 0; } return 1; }
這里給出的函數是一個“剪枝函數”,(例如現在走到了第三行,就要循環第一、第二行,找出前兩行的皇后存在的位置,然后與第三行的皇后位置比較-----①如果第三行皇后與以上兩行任意一個皇后在同一列②或者同一對角線(主對角線或者非主對角線均可以,代碼:abs(rows-i)==abs(columns-location[i])),那么返回0,否則說明第三行的這個位置可以放皇后,就返回1)
此時我們知道了“剪枝函數”也就體現了回溯算法的思想了,因為按照我的理解,回溯就是(完全遍歷的所有情況-大部分一開始就不滿足條件的情況),所以這個函數其實很重要。
之后我們寫出來回溯的函數
int Queen(int row){ if(row == 8) { int current_max=0; for(int i=0;i<8;i++){ current_max+=num[i][location[i]]; } maxn=max(current_max,maxn); } else{ for(int n=0;n<8;n++){ if(valid(row,n)){ location[row]=n; Queen(row+1); } } } }
在這個函數中,我們傳入的row是行號,第一個if是跳出循環的條件——當row循環了八次之后完成循環(也就說明這八次循環得到了一種最優解的情況),else里面是循環八次分別找每一行的符合要求的解。
如果滿足valid,那么記錄第row行的列號,然后遞歸到下一層函數。
*****這里我們為什么要定義全局變量呢?這里,我們要知道程序在計算的時候是按照順序執行的。只有8^8種情況中的第一種結束了,才會計算下一種情況。所以這個全局變量會被每一個子問題分別使用,並且下一個子問題會不斷覆蓋上一個子問題的全局變量中的值。
之后我們有了n皇后的基礎之后,解決2n或者多n皇后問題就很簡單了。
在2n皇后的要求中(我開始寫的計蒜課的算法題目),我們知道它多了一個條件,就是我令一部分位置不能放棋子。這個時候我們就要在 Queen函數中循環列的時候判斷一下這個位置是否可用,只有可用的時候才能進入判讀。
而2n皇后其實就是先計算第一個n皇后,然后得出來一個n皇后的表,之后在計算另一個皇后,這個時候第二種皇后的情況就要除去第一個n皇后已經放入的位置。就是相當於那個”能否使用表”稍微復雜了一點而已。。。
這里放上代碼:
#include<iostream> #include<cmath> using namespace std; int board[8][8]; int all=0; int black_location[8]; int white_location[8]; int w_valid(int rows,int columns){ for(int i=0;i<rows;i++){ if(columns==white_location[i]||abs(rows-i)==abs(columns-white_location[i])) return 0; } return 1; } int b_valid(int rows,int columns){ for(int i=0;i<rows;i++){ if(columns==black_location[i]||abs(rows-i)==abs(columns-black_location[i])) return 0; } return 1; } int Queen_b(int cur,int n){ if(cur==n){ all++; } else{ for(int h=0;h<n;h++){ if(h==white_location[cur]||board[cur][h]==0) continue; else{ if(b_valid(cur,h)){ black_location[cur]=h; Queen_b(cur+1,n); } } } } } int Queen_w(int cur,int n){ if(cur==n) { Queen_b(0,n); } else{ for(int i=0;i<n;i++){ if(board[cur][i]==0) continue; if(w_valid(cur,i)){ white_location[cur]=i; Queen_w(cur+1,n); } } } } int main(){ int n; cin>>n; for(int i=0;i<n;i++){ for(int h=0;h<n;h++) cin>>board[i][h]; } Queen_w(0,n); cout<<all; }
多注意細節就好,代碼量有點大,有什么不懂的地方大家給我留言。
--------------------------------------------------------------------------------------Made By Pinging
