目录
题目
【题目描述】
在国际象棋棋盘上放置八个皇后,要求每两个皇后之间不能直接吃掉对方。
【输入】
(无)
【输出】
按给定顺序和格式输出所有八皇后问题的解(见样例)。
【输入样例】
(无)
【输出样例】
No. 1
1 0 0 0 0 0 0 0
0 0 0 0 0 0 1 0
0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 1
0 1 0 0 0 0 0 0
0 0 0 1 0 0 0 0
0 0 0 0 0 1 0 0
0 0 1 0 0 0 0 0
No. 2
1 0 0 0 0 0 0 0
0 0 0 0 0 0 1 0
0 0 0 1 0 0 0 0
0 0 0 0 0 1 0 0
0 0 0 0 0 0 0 1
0 1 0 0 0 0 0 0
0 0 0 0 1 0 0 0
0 0 1 0 0 0 0 0
...以下省略
题目传送门
算法解析
做法
因为我们要输出好几种摆法,所以可以用递归:
void dfs(int x)
其中,x 表示行
输出
可以记下每行皇后摆放在第几列,即 a[i] 表示第 i 行的皇后在第几列上
输出代码(仅一次):
printf("No. %d\n", ++cnt);
for(int i = 1; i < 9; ++i) {
for(int j = 1; j < 9; ++j) {
if(a[j] == i)
printf("1 ");
else
printf("0 ");
}
puts("");
}
标记皇后占领的行、列、左斜线(/)、右斜线(\)
首先,我们递归是以行作为参数的,所以行不用考虑
列呢,可以用一个数组记录,即 v1[i] 表示第 i 列有没有皇后(有为 1,无为 0)
左斜线和右斜线嘛……
可以试着找一找他们的规律:
图中,蓝色为左斜线,绿色为右斜线
可以发现,左斜线的横纵坐标规律为:横纵坐标之和相同,右斜线的横纵坐标规律为:横纵坐标之差相同
但是需要注意的是:右斜线横纵坐标之差有可能为负数,如下图:
其中行减列为 -1,但数组下标只能为 0 或正数,所以我们可以把它加 8,因为最大就是 0 - 8 = -8
综上所述,我们可以用两个数组来表示左斜线和右斜线:
v2[x - y + 8] 表示坐标为 (x, y) 的点所在的右斜线有没有皇后(有为 1,无为 0)
v3[x + y] 表示坐标为 (x, y) 的点所在的左斜线有没有皇后(有为 1,无为 0)
递归边界,完善输出
到达递归边界的时候就可以输出了,输出我们在上面已经讨论完了,那么就要考虑递归边界了
我们从递归参数 x 入手,x 既可以表示行,也可以表示当前正在放第几个皇后
那么等到要放第九个皇后时,就可以输出了
所以递归边界就可以确定了:x > 8
结合上面的输出就拼出来了完整的输出过程
if(x > 8) {
printf("No. %d\n", ++cnt);
for(int i = 1; i < 9; ++i) {
for(int j = 1; j < 9; ++j) {
if(a[j] == i)
printf("1 ");
else
printf("0 ");
}
puts("");
}
}
摆放皇后
判断是否可以摆放
然后,我们就可以枚举皇后在第几列了
我们可以利用 for 循环来枚举,即:
for(int i = 1; i < 9; ++i)
现在,我们已经枚举到了坐标为 (x, i) 的点,我们就可以看该点有没有被其他皇后占领,即:
if(!v1[i] && !v2[x - i + 8] && !v3[x + i])
上面已经说过了,v1[i] 表示第 i 列有没有皇后(有为 1,无为 0)、v2[x - y + 8] 表示坐标为 (x, y) 的点所在的右斜线有没有皇后(有为 1,无为 0)、v3[x + y] 表示坐标为 (x, y) 的点所在的左斜线有没有皇后(有为 1,无为 0),所以判断只要他们都是 0 就行了
摆放
摆放,其实就是占领行、列、左斜线和右斜线,即把三个数组都赋成 1 就行了
偶,对了,还要把用于输出的 a[x] 赋成 i,因为 a[x] 表示第 x 行的皇后在第几列上
a[x] = i;
v1[i] = 1;
v2[x - i + 8] = 1;
v3[x + i] = 1;
递归
摆放完第 x 个之后,要摆放第 x + 1 个,这里就需要递归了,递归 x + 1:
dfs(x + 1);
回溯
别忘记回溯,这也是很重要的!!!
我们摆放完之后,需要把它撤回,尝试下一种摆法,因此需要回溯
这里的回溯,其实就是将占领撤回,将三个数组赋成 0:
v1[i] = 0;
v2[x - i + 8] = 0;
v3[x + i] = 0;
题解
最后将我们的“一块块拼图”拼在一起,就是最终的AC代码了:
#include <iostream>
using namespace std;
int a[9], v1[101], v2[101], v3[101], cnt; // a[i] 表示第 i 行的皇后在第几列上
// v1[i] 表示第 i 列有没有皇后(有为 1,无为 0)
// v2[x - y + 8] 表示坐标为 (x, y) 的点所在的右斜线(\)有没有皇后(有为 1,无为 0)
// v3[x + y] 表示坐标为 (x, y) 的点所在的左斜线(/)有没有皇后(有为 1,无为 0)
void dfs(int x) {
if(x > 8) { // 八个皇后放完了,输出
printf("No. %d\n", ++cnt);
for(int i = 1; i < 9; ++i) {
for(int j = 1; j < 9; ++j) {
if(a[j] == i)
printf("1 ");
else
printf("0 ");
}
puts("");
}
} else {
for(int i = 1; i < 9; ++i) {
if(!v1[i] && !v2[x - i + 8] && !v3[x + i]) { // 坐标为 (x, i) 的点没有被其他皇后占领,此点可以放皇后
a[x] = i; // 第 x 行的皇后放在了第 i 列
v1[i] = 1; // 占领列
v2[x - i + 8] = 1; // 占领右斜线
v3[x + i] = 1; // 占领左斜线
dfs(x + 1); // 在下一行放皇后
v1[i] = 0; // 回溯
v2[x - i + 8] = 0;
v3[x + i] = 0;
}
}
}
}
int main() {
dfs(1); // 从第一行开始放
return 0;
}
(由于一共有 92 种摆放方法,在此就不贴运行结果了)
提交结果
提交一下~
AC 了!
尾声
如果此博客对您有帮助的话,就帮忙点个赞,加个关注吧!
最后,祝您(或您的团队)在 OI 的路上一路顺风!
再见!
ヾ( ̄▽ ̄)Bye~Bye~