ACwing843 - n皇后问题(经典dfs)

n-皇后问题是指将 n 个皇后放在 n∗n 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。

在这里插入图片描述

现在给定整数n,请你输出所有的满足条件的棋子摆法。

输入格式

共一行,包含整数n。

输出格式

每个解决方案占n行,每行输出一个长度为n的字符串,用来表示完整的棋盘状态。

其中”.”表示某一个位置的方格状态为空,”Q”表示某一个位置的方格上摆着皇后。

每个方案输出完成后,输出一个空行。

输出方案的顺序任意,只要不重复且没有遗漏即可。

数据范围
1 ≤ n ≤ 9

输入样例:

4

输出样例:

.Q…
…Q
Q…
…Q.

…Q.
Q…
…Q
.Q…

题目大意:

要求输出方案数,有n * n 的棋盘,需要放n皇后(用Q表示),空地用’ . ’ 表示,关于皇后放置有以下几个要求:

  • 任意两个皇后不能在同一行
  • 任意两个皇后不能在同一列
  • 任意两个皇后不能再同一对角线

要求输出摆好的棋盘。

解题思路:

经典n皇后问题,用dfs解决,数据范围很小,dfs暴搜 + 剪枝即可AC,这里介绍两种搜索思路:

思路一:从第1行搜索到第n行, 按列判断。
这个思路是按行搜索,标记每一列和主次对角线,按行搜索可以保证任意两个皇后都不在同一行,然后去搜这一行的1 - n 列,开标记数组book标记这一列上是否有皇后,gh和ugh标记主次对角线,关于主次对角线:

  • y = - x + b 对于每一个点(y + x)都有唯一的 b 值对应
  • y = x + b 对于每一个点(y - x) 有唯一的b 值对应

因为第二个涉及到了y - x有可能负数,所以开大一点标记数组,并把式子映射为y - x + n 。因为都有唯一一个 b 所对应,所以可以根据 x ± y 的值是否被标记过判断该对角线上有无皇后,之后按照dfs模板去搜,状态改变状态还原,如果x > n 则证明搜完了且一定存在解(因为继续往下搜索的前提是这一行可以找到放皇后的位置),输出并return;

Code1:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <iomanip>
#include <sstream>
#include <cmath>
#include <vector>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#define lowbit(x) x & (-x)

using namespace std;

typedef long long ll;
typedef pair<int, int> pii;

const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const int N = 25;

int n;
char mp[N][N];
bool gh[N], ugh[N], book[N];

void dfs(int s)
{
    if (s == n + 1)
    {
        for (int i = 1; i <= n; i ++) puts(mp[i] + 1);
        puts("");
        return;
    }

    for (int i = 1; i <= n; i ++)
    {
        if (!book[i] && !gh[s + i] && !ugh[n + s - i])
        {
            book[i] = true;
            gh[s + i] = true;
            ugh[n + s - i] = true;
            
            mp[s][i] = 'Q';//改状态并继续搜
            dfs(s + 1);
            
            book[i] = false;
            gh[s + i] = false;
            ugh[n + s - i] = false;
            mp[s][i] = '.';//还原状态
        }
    }

    return;
}

int main()
{
    scanf("%d", &n);

    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= n; j ++)
            mp[i][j] = '.';
    
    dfs(1);//从第一行开始搜
    
    return 0;
}

思路二:枚举所有格子的状态
顾名思义:每个格子有放和不放两种状态,对于每个格子枚举即可。每次枚举完以后枚举(x, y + 1, s),如果y > n 说明这行枚举完了,y = 1, x++, 继续枚举下一行,如果x > n 则说明整个棋盘枚举完了,判断s 是否等于 n ,这里的 s 是指当前已经放下了几个皇后,如果等于n则输出棋盘,之后return。

每个格子只有放和不放,不放的话很好理解,直接dfs(x, y + 1, s),如果放的话需要判断一下这个格子是不是能放皇后,需要开一个行标数组,列标数组,主次对角线,都判断一下,然后选择是否放皇后即可。

Code2:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <iomanip>
#include <sstream>
#include <cmath>
#include <vector>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#define lowbit(x) x & (-x)

using namespace std;

typedef long long ll;
typedef pair<int, int> pii;

const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const int N = 25;

int n;
char mp[N][N];
bool gh[N], ugh[N], col[N], row[N];

void dfs(int x, int y, int s)
{
    if (y > n)
    {
        y = 1;
        x++;
    }
    
    if (x > n)
    {
        if (s == n)
        {
            for (int i = 1; i <= n; i ++) puts(mp[i] + 1);
            puts("");
        }
        return;//return一定要写在 if 外面,因为不管方案是否可行都搜完了要返回,不然会MLE。
    }

    dfs(x, y + 1, s);
    
    if (!row[x] && !col[y] && !gh[x + y] && !ugh[x - y + n])//判断是否可以放皇后
    {
        row[x] = col[y] = gh[x + y] = ugh[x - y + n] = true;//行标列标主次对角线都判断一下
        mp[x][y] = 'Q';//状态修改/还原
        dfs(x, y + 1, s + 1);
        row[x] = col[y] = gh[x + y] = ugh[x - y + n] = false;
        mp[x][y] = '.';
    }

    return;
}

int main()
{
    scanf("%d", &n);

    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= n; j ++)
            mp[i][j] = '.';
    
    dfs(1, 1, 0);//从(1, 1)开始搜,当前已经放了0个皇后
    
    return 0;
}
posted @ 2020-08-20 22:58  Hayasaka  阅读(121)  评论(0编辑  收藏  举报