八皇后问题

问题描述

在国际象棋中,有一个非常强势的棋子——皇后,他的走法可以在网上参考一下,概括来说就是可以沿着行、列、与对角线平行的线走。而在计算机中有一个关于他的经典问题,8皇后,就是在8行8列的棋盘上放入8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法,而n皇后就是在n行n列棋盘上放入n个皇后的情况。

解法

我们针对他们的限制条件展开,可以开两个一维数组row[]和col[]来分别存储行列上的信息,按行进行举例,如果在第4行存在一个皇后,就将row[4]赋值为1,这样如果还有别的皇后想放在第4行,只要加个if(row[4]==1)就可以发现此行已经被占了。

行列很好处理,关键的问题来了,斜线怎么办,此时我们就可以引入一个数学坐标系了,我们发现如果两个皇后能在斜线上相互进攻,那么他们两点所构成的直线一定和棋盘的主对角线或次对角线平行,而且由于棋盘是正方形的,所以这两条的斜率也分别为1或-1。

先来看主对角线的情况
在这里插入图片描述

由于有8行8列,所以主对角线的函数也就如图所示,为y=8-x,我们来进行移向,得到y+x=8,我们发现了一个重要的信息,即在此线上的所有点的x,y坐标值进行加和是一个同样的常数8,那么和主对角线平行的其他线是什么情况呢,再来看两个函数

y=7-x(绿色)
在这里插入图片描述
他们的x,y坐标值进行加和是常数7

和y=10-x(黄色)
在这里插入图片描述
他们的x,y坐标值进行加和是常数10

那么类推到一般情况呢,y=c-x,进行移向得到y+x=c,也就是说我们可以开一个数组a[],用a[c]=1,来代表这个斜线上有皇后!但是数组a[]的大小应该开多大呢,我们发现c最小的时候是过(0,0)点的情况,最大的时候是过(8,8)点的情况,那么c最小为0,最大为16,类推到n皇后呢,就是最小为0(过点(0,0)),最大为2n(过点(n,n)),所以开一个数据大小为n*2+1的数组a[]就够了。

再来看次对角线的情况
在这里插入图片描述

他的函数是y=x,似乎并没有和主对角线那么好的规律,再来换个平行于的他的线看看

y=x+3(绿色)
在这里插入图片描述
我们发现如果移向为y-x=3,似乎也可以得到类似主对角线那样方便存储的性质,那就是开个数组b[],当b[y-x]=1,代表在此条斜线上有皇后,但是这样似乎有点问题,那就是下面这种情况

y=x-3(黄色)
在这里插入图片描述
哦天呀,y-x居然出现了负值,那么怎么存储呢,我们可以找一找此时数据的范围,设直线为y=x+c,即y-x=c,c最小的情况为过(8,0)的情况,最大情况为过(0,8)的情况,也就是c的取值范围为[-8,8],那么类推到n皇后呢?c的取值范围为[-n(过点(0,n)),n(过点(n,0))],欸嘿,我们发现可以令c+=n,这样c的取值范围就被映射到了[0,2n],也就是当数组b[c]==1代表了在图中y=x+c-n这条线上有皇后,此时数组的大小也出现了,占用空间和主对角线的情况一样,开个大小为2*n+1的数组b[]。

理论知识在这里就介绍完了,也就是我们可以开4个数组分别存行、列、主对角线平行的斜线、次对角线平行的斜线皇后的存在情况,那么代码改怎么实现呢,此处可以自己尝试一下,小提示,可以使用递归进行方案数求值,而且还可以少开一个行或者列的数组,洛谷的八皇后题目

开始介绍洛谷此题的解法,可以使用递归,来递归每一行皇后的放置情况,此时由于是对行进行的递归,所以也就不用开行数组了。

#include<cstdio>
#include<iostream>
#include<algorithm>
#define ll long long

using namespace std;

const int N=1000;

ll n,idx,a[N],b[N],col[N],ans[N];
//idx用来存储已经找到了多少解 
//这里的a对用主对角线的情况,b对应次对角线的情况,col为列
//因为开的数组远大于所需要的,所以a和b数组不用开2N+1空间了

void dfs(ll now){  //now为已经遍历到了第now行 
	if(now==n+1){  //如果now==n+1,显然我们已经遍历完了所有的行 
		idx++;     //所以答案可以++ 
		if(idx<4){ //如果解是前3个,根据题目要求要输出其摆放方案 
			printf("%d",ans[1]);
			for(int i=2;i<=n;i++) printf(" %d",ans[i]);
			printf("\n");
		}
		return ;
	}
	for(int i=1;i<=n;i++){
		if(a[now+i]||b[now-i+n]||col[i]) continue;
		//对应着理论中的该点所在的主对角线,次对角线,列上是否有皇后的情况 
		a[now+i]=b[now-i+n]=col[i]=1;
		//如果没有就代表可以放入,此时要把三个数组的情况全改为1,代表在此处现在有皇后了 
		ans[now]=i;
		//记录答案的方案 
		dfs(now+1);
		//进入下一行 
		a[now+i]=b[now-i+n]=col[i]=0; 
		//此点放完后,我们要拿走此处的皇后,继续在次行之后的位置放入皇后,
		//所以要把此点的状态先清空 ,也就是全部赋值为0 
	}
}

int main(){
	scanf("%lld",&n);
	dfs(1);
	printf("%lld",idx);
}


由于用的暴力解法,我们也发现此代码的时间效率并不是很好,而且空间上,推测程序栈中应该也存了不少东西,不过由于汇编的能力并不是很强,也就不再继续深入探讨了。
蒟蒻一枚,如有错误,欢迎指正。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值