信息学奥赛一本通 1213:八皇后问题

文章详细介绍了八皇后问题的解决策略,主要使用递归和回溯算法,通过标记行、列、左斜线和右斜线来判断皇后是否能安全放置,并提供了C++的实现代码。文章还包含了输出不同解的方法和递归边界的确定。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

题目

题目传送门

算法解析

做法

输出

标记皇后占领的行、列、左斜线(/)、右斜线(\)

递归边界,完善输出 

摆放皇后

判断是否可以摆放

摆放

递归

回溯

题解

提交结果

尾声


题目

【题目描述】

在国际象棋棋盘上放置八个皇后,要求每两个皇后之间不能直接吃掉对方。

【输入】

(无)

【输出】

按给定顺序和格式输出所有八皇后问题的解(见样例)。

【输入样例】

(无)

【输出样例】

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 
...以下省略

题目传送门

信息学奥赛一本通(C++版)在线评测系统

算法解析

做法

因为我们要输出好几种摆法,所以可以用递归:

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~

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值