USACO1.5.4 Checker Challenge跳棋的挑戰 解題報告(N皇后 回溯法)
阿新 • • 發佈:2019-02-07
Description
檢查一個如下的6 x 6的跳棋棋盤,有六個棋子被放置在棋盤上,使得每行,每列,每條對角線(包括兩條主對角線的所有對角線)上都至多有一個棋子。 列號
0 1 2 3 4 5 6 ------------------------- 1 | | O | | | | | ------------------------- 2 | | | | O | | | ------------------------- 3 | | | | | | O | ------------------------- 4 | O | | | | | | ------------------------- 5 | | | O | | | | ------------------------- 6 | | | | | O | | -------------------------
上面的佈局可以用序列2 4 6 1 3 5來描述,第i個數字表示在第i行的相應位置有一個棋子,如下: 行號 1 2 3 4 5 6 列號 2 4 6 1 3 5 這只是跳棋放置的一個解。請遍一個程式找出所有跳棋放置的解。並把它們以上面的序列方法輸出。解按字典順序排列。請輸出前3個解。最後一行是解的總個數。 特別注意: 對於更大的N(棋盤大小N x N)你的程式應當改進得更有效。不要事先計算出所有解然後只輸出,這是作弊。如果你堅持作弊,那麼你登陸USACO Training的帳號將被無警告刪除
Input
一個數字N (6 <= N <= 13) 表示棋盤是N x N大小的。
Output
前三行為前三個解,每個解的兩個數字之間用一個空格隔開。第四行只有一個數字,表示解的總數。
Sample Input
6
Sample Output
2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
4
N皇后,很經典的回溯法題目.不過自己手擼出來還是蠻有成就感的
需要注意的是這個題時間卡的恨死,我開了一個大小最多為13的map居然都T了....
通常我們用a[i][j]來表示第i行j列的狀態,但是n皇后每一行只能有1個皇后,所以一維陣列就能存下所有狀態了
用a[i] = j來表示第i行第j列放置了皇后,就避免了判斷行,然後對於判斷列,我們可以單開一個數組v,記錄列的狀態,比較難處理的是所有對角線的情況
這個題判斷對角線必須採用O(1)的時間複雜度,我們這裡設兩個陣列:第一個判斷這種對角線:↙,規律為行+列為一個固定的數,第二個判斷這種:↘,規律為行-列為固定的數(由於行-列可能為負數,這裡我統一加上12,這樣就不會發生陣列越界的情況了
有了這四種狀態的O(1)的判法,剩下的直接DFS就好
#include <map>
#include <cstdio>
#include <iostream>
#define ifor(a) for(int i=1;i<=a;i++)
using namespace std;
const int maxn = 20;
int n;
int a[maxn];
int res[maxn];
int cnt = 1;
int sum;
bool v[maxn];
bool l[maxn*2];
bool r[maxn];
void dfs(int k)
{
if(k == n+1){
sum ++;
if(sum <=3){
ifor(cnt-1){
if(i>=2)
putchar(' ');
printf("%d",res[i]);
}
putchar('\n');
}
return;
}
ifor(n){
if(!v[i]&&!r[i+k]&&!l[i-k+12]){ //判斷列和對角線
a[k] = i;
r[i+k] = v[i] = l[i-k+12] = true;
res[cnt++] = i;
dfs(k+1);
cnt--;
r[i+k] = v[i] = l[i-k+12] = false;
}
}
}
int main()
{
scanf("%d",&n);
dfs(1);
printf("%d\n",sum);
return 0;
}