八皇后问题
在一个8×8的棋盘上放置8个皇后,使得他们不相互攻击。每个皇后的攻击范围是同行同列和同对角线,求出所有解的个数
首先简化问题,如果符合要求放满8个皇后,那么肯定保证每一行和每一列有且仅有一个皇后。那么显然问题就变成了如何在一行或者一列放满8皇后。接着就可以转化为求符合要求的8的全排列,那么求全排列我们显然可以用dfs来解决
既然是逐行放置的,那么皇后肯定不会横向攻击,因此只需要检查是否纵向或者斜向攻击即可。那么怎么判断呢?我们可能会想到用一个二维的vis数组来把可攻击的区域全部标记。这样显然时间复杂度有点大。通过紫书的学习,有一个绝妙的利用棋盘坐标性质的方法,我们设每行每列的坐标值均为0-7:
求出每个格子(x,y)的y-x差值,会发现同一主对角线的差值相同
求出每个格子(x,y)的x+y的和,会发现同一副对角线的和相同
那么我们由ans==ans[j]判断是否同一列,cur-ans[cur]==j-ans[j]判断是否同一主对角线,cur+ans[cur]==j+ans[j]判断是否同一副对角线:
int ans[1005];
int n,cnt;
void solve(int cur){ //cur初始为0
if(cur==n) cnt++; //只要走到这里,已经放满8个皇后就得出一组解
else for(int i=0;i<n;i++){ //尝试把第cur行放到第i列
int ok=1;
ans[cur]=i;
for(int j=0;j<cur;j++) //检查是否和前面放过的皇后冲突
if( ans[cur]==ans[j] || cur-ans[cur]==j-ans[j] || cur+ans[cur]==j+ans[j] ){ ok=0; break; }
if(ok) solve(cur+1);
}
}
似乎节点数很难进一步减少了,但是程序效率可以继续提高:引入一个二维数组vis[3][maxn],vis[0][i]标识第i列是否冲突,vis[1][cur+i]标识副对角线是否冲突,鉴于判断y-x差值时可能为负数,那么就再都加上n变成正数,即vis[2][cur-i+n]判断主对角线是否冲突
bool vis[3][1005];
void solve(int cur){
if(cur==n) cnt++;
else for(int i=0;i<n;i++){
if(!vis[0][i] && !vis[1][cur+i] && !vis[2][cur-i+n]){
vis[0][i]=vis[1][cur+i]=vis[2][cur-i+n]=1;
solve(cur+1);
vis[0][i]=vis[1][cur+i]=vis[2][cur-i+n]=0;
}
}
}