【刷题日记】经典题目:八皇后问题(DFS回溯搜索)

八皇后问题题面

[USACO1.5] 八皇后 Checker Challenge(洛谷P1219)

一个如下的 \(6 \times 6\) 的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。

上面的布局可以用序列 \(2\ 4\ 6\ 1\ 3\ 5\) 来描述,第 \(i\) 个数字表示在第 \(i\) 行的相应位置有一个棋子,如下:

行号 \(1\ 2\ 3\ 4\ 5\ 6\)

列号 \(2\ 4\ 6\ 1\ 3\ 5\)

这只是棋子放置的一个解。请编一个程序找出所有棋子放置的解。
并把它们以上面的序列方法输出,解按字典顺序排列。
请输出前 \(3\) 个解。最后一行是解的总个数。

输入格式

一行一个正整数 \(n\),表示棋盘是 \(n \times n\) 大小的。

输出格式

前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。

样例 #1

样例输入 #1

6

样例输出 #1

2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
4

提示

【数据范围】
对于 \(100\%\) 的数据,\(6 \le n \le 13\)

题目翻译来自NOCOW。

USACO Training Section 1.5

DFS法题解

题目背景

在国际象棋中,“皇后”这类棋子可以攻击同一行、同一列、同一主对角线或同一副对角线的所有棋子。我们想在8*8的棋盘上放下正好8个皇后,使它们互相无法攻击彼此,问有多少种不同的放法?(区分对称放法)这就是八皇后问题的背景。后来,大家将其发展成了N皇后问题,即N*N的棋盘上放N个互不攻击的皇后。此题解决\(6 \le N \le 13\)的情况。

分析

在数据量较小的情况下,可以选择暴力搜索。
显然,同一行不可能存在两个及以上皇后,所以我们只需要找每行的皇后在第几列然后输出。根据我们的模拟思想,去模拟一个一个放下皇后的过程。我们可以先放第一行皇后,然后放第二行,第三行……如果到最后没有一个位置可以用来放皇后,那就说明上一步本身就是错的,我们需要拿起上一个皇后重新放。如果最后放下了所有皇后,我们又需要拿起最后的皇后重新放,去找其他解。也就是说,无论如何我们都需要回溯。
继续分析,本题的难点是如何保存某种状态,并在一段时间运行之后回到这种状态(即回溯)。分析题目,注意到,设行为i,列为j的格子,容易发现,同列格子j相等,同主对角线及其平行线的格子i-j(为了避免i<j时i-j<0,这里写作i-j+15)相等,同副对角线及其平行线的格子i+j相等。容易证明,这三者为充要条件。
所以我们利用这三者创建三个数组,去比较不同行的queen是否相互攻击。只要这三者有一个成立,则相互攻击。三个数组中,我们把某行某列对应的棋子攻击范围标记好,则可直接通过判断三者是否有一个成立,来判断新的queen是否在其他queen攻击范围内。要回溯也很简单,只需要将上一步三者的值改回默认值0即可。

AC代码
#include <iostream>
using namespace std;

long long column[50],main_diag[50],sub_diag[50],ans[15];
long long n,all_sum = 0;

void save(long long r,long long c){
	column[c] = 1;
	main_diag[r-c+15] = 1;
	sub_diag[r+c] = 1;
}

void unsave(long long r,long long c){
	column[c] = 0;
	main_diag[r-c+15] = 0;
	sub_diag[r+c] = 0;
}

void dfs(long long now)	//寻找第now个皇后的位置并找下一个
{
	if(now>n)	//找到一解,进行结算
	{
		all_sum ++;
		//输出答案
		if(all_sum<=3){
			for(long long i = 1;i<=n;i++)	printf("%lld ",ans[i]);
			printf("\n");
		}
		return;
	}
	for(long long i = 1;i<=n;i++){	//遍历本行所有可以放皇后的位置
		if(column[i] || main_diag[now-i+15] || sub_diag[i+now])	continue;	//排除被攻击区域
		ans[now] = i;	//保存答案
		save(now,i);	//保存当前新增影响区域
		dfs(now+1);		//寻找下一个皇后
		unsave(now,i);	//回溯,删除当前新增影响区域
	}
	return;
}

int main(){
	scanf("%lld",&n);
	dfs(1);
	printf("%lld",all_sum);
	return 0;
}
posted @ 2025-01-11 23:58  Alkaid16  阅读(251)  评论(0)    收藏  举报