計蒜課--2n皇后、n皇后的解法(一般操作hhh)


給定一個 n*nnn 的棋盤,棋盤中有一些位置不能放皇后。現在要向棋盤中放入 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

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM