題目
在一張N∗N的國際象棋棋盤上,放置N個皇后,使得所有皇后都無法互相直接攻擊得到,(皇后可以直接攻擊到她所在的橫行,豎列,斜方向上的棋子),現在輸入一個整數N,表示在N∗N的棋盤上放N個皇后,請輸出共有多少種使得所有皇后都無法互相直接攻擊得到的方案數。 例如下面這樣的擺法,是4皇后的一個解 (1代表有皇后,0代表沒有)
0 1 0 0
0 0 0 1
1 0 0 0
0 0 1 0
輸入
一個整數N,代表皇后的個數
輸出
輸出方案數
樣例輸入
樣例輸入1
4
樣例輸入2
8
樣例輸出
樣例輸出1
2
樣例輸出2
92
一、DFS+回溯(1)
設已經放好的皇后坐標為(i,j),待放入的皇后坐標為(r,c),則它們滿足以下關系:
(1)不同行,即 i ≠ r;
(2)不同列,即 j ≠ c;
(3)不在斜對角線上,即 |i-r| ≠ |j-c|.
可以在一行逐列嘗試,這樣就不用考慮(1)了。
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int n, tot = 0;
int col[15] = {0}, ans[15] = {0}; //col[i]的值為第i行的皇后的列數的值,即j,ans[]數組用來存放結果
bool check(int c, int r) //檢查是否和已經放好的皇后沖突
{
for (int i = 0; i < r; i++)
if (col[i] == c || (abs(col[i] - c) == abs(i - r))) //因為是逐行放置,所以只考慮縱向和斜向
return false;
return true;
}
void dfs(int r,int m) //在第r行放皇后,m表示行數
{
if(r==m){ //r==m,即皇后放到最后一行,滿足條件,tot++,返回;
tot++;
return;
}
for(int c=0;c<m;c++) //在此行逐列嘗試
if(check(c,r)){ //檢查是否沖突
col[r]=c; //不沖突就在此列放皇后
dfs(r+1,m); //轉到下一行繼續嘗試
}
}
int main()
{
cin>>n;
for (int i = 0; i <= 13; i++) //算出所有N皇后的答案,先打表,不然會超時
{
memset(col, 0, sizeof(col)); //清空col,准備計算下一個N皇后問題
tot = 0;
dfs(0,i);
ans[i] = tot;
}
cout << ans[n] << endl;
return 0;
}
在上述程序中,dfs()一行行放置皇后,時間復雜度為O(N!);check()判斷沖突,時間復雜度為O(N),總的為O(N*N!)!非常的高。
二、DFS+回溯(2)
#include <iostream>
#include <algorithm>
using namespace std;
int tot = 0, n;
int col[20] = {0}; //col[]含義和上面一樣,表示列的值j
void dfs(int r)
{
if (r == n)
tot++; //達到遞歸邊界,方案數加一
else
for (int i = 0; i < n; i++)
{
int flag = 1;
col[r] = i; //嘗試把第r行皇后放在第i列
for (int j = 0; j < r; j++) //檢查是否和前面的皇后沖突
if (col[r] == col[j] || r - col[r] == j - col[j] || r + col[r] == j + col[j])
{
flag = 0;
break;
}
if (flag)
dfs(r + 1);
}
}
int main()
{
cin >> n;
dfs(0);
cout << tot << endl;
return 0;
}
優化:
既然皇后是逐行放置的,因此只需要檢查縱向和斜向是否攻擊即可。縱向好判斷,對於斜向,我們可以把格子的坐標設置成(x,y),用(y-x)標識主對角線的值,用(y+x)表示副對角線的值(當然也可以反過來)。這是會發現對於主或副每一條對角線的值是一樣且唯一的(其實也不太嚴謹)。如下圖所示:
在此,我們設置一個二維數組vis[3][2n](為什么是2n接下來會講到),用vis[0][i]表示i列是否放置皇后,vis[1][r+i](即y+x)表示副對角線是否皇后,用vis[2][r-i+n](即y-x,+n是防止出現負數)表示主對角線是否放置皇后。
接下來就簡單了,和上面想法一樣,對於每一行,從0開始逐列嘗試放置皇后,如果在這行的i列中,vis[0][i]和vis[1][r+i]和vis[2][r-i+n]都為零,即這個格子(r,i)的縱向和兩個斜向都沒有皇后,就在此處放置皇后,並標記這個格子的縱向和兩個斜向都已經不能放置皇后(因為這個位置放了新皇后)也就是令vis[0][i] = vis[1][r+i] = vis[2][r-i+n] = 1;繼續嘗試下一行,最后別忘了取消標記(vis[0][i] = vis[1][r+i] = vis[2][r-i+n] = 0)。
主要思路就是這樣,有一些需要注意的細節。因為r-i+n的值最大會達到2n-1,所以最少要設置vis[3][2n],當然大一點會更好。還有就是細心的同學會發現在上面的主對角線中,y-x+n后會有不同的對角線有相同值,其實不用太在意,因為判斷條件是同時判斷三個(縱向和兩個斜向),而對於每一個格子,其三向坐標是唯一的(y,y-x(+n),y+x)。
下面是代碼:
#include <iostream>
#include <algorithm>
using namespace std;
int vis[3][45]={0},c[20]={0},tot=0,n; //c[i]表示在i行皇后放置的列的值j,主要為了打印結果;vis[3][]是用來判斷的數組,tot存放答案
void dfs(int r)
{
if(r==n) tot++; //遞歸邊界,走到最后一行說明方案可行,tot++
else for(int i=0;i<n;i++){ //在r行逐列嘗試放皇后
if(!vis[0][i] && !vis[1][r+i] && !vis[2][r-i+n]){ //如果(r,i)格子的三個方向都沒有皇后
c[r] = i; //在i列放置皇后,如果不用打印解,可以省略整個c[]
vis[0][i] = vis[1][r+i] = vis[2][r-i+n] = 1; //標記
dfs(r+1); //向下一行嘗試
vis[0][i] = vis[1][r+i] = vis[2][r-i+n] = 0; //取消標記
}
}
}
int main()
{
cin>>n;
dfs(0);
cout<<tot<<endl;
return 0;
}
三、N皇后的一些解
n solution(n)
1 1
2 0
3 0
4 2
5 10
6 4
7 40
8 92
9 352
10 724
11 2680
12 14200
13 73712
14 365596
15 2279184
16 14772512
17 95815104
18 666090624
19 4968057848
20 39029188884
21 314666222712
22 2691008701644
23 24233937684440
24 227514171973736
25 2207893435808352