问题描述
在国际象棋中,有一个非常强势的棋子——皇后,他的走法可以在网上参考一下,概括来说就是可以沿着行、列、与对角线平行的线走。而在计算机中有一个关于他的经典问题,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);
}
由于用的暴力解法,我们也发现此代码的时间效率并不是很好,而且空间上,推测程序栈中应该也存了不少东西,不过由于汇编的能力并不是很强,也就不再继续深入探讨了。
蒟蒻一枚,如有错误,欢迎指正。