题目内容:
n皇后游戏要求在一个n×n的棋盘上放置n个皇后,使n个皇后互相不攻击(攻击的含义是有两个皇后在同一行或同一列或同一对角线上)。求有多少种放置方法。
① 回溯法
未经优化的暴力解法。虽然很暴力,而且一般就会TLE,但是简单易懂,会给一些优化代码做铺垫。
int c[N];
void dfs(int cur,int n){
if(cur==n) ans++;
else for(int i=0;i<n;i++){
int flag=1;c[cur]=i;
for(int j=0;j<cur;j++)
if(c[cur]==c[j]||c[cur]-cur==c[j]-j||c[cur]+cur==c[j]+j){
flag=0;
break;
}
if(flag)
dfs(cur+1,n);
}
}
稍微有点优化的解法,对于每一次判断皇后的放置,再次遍历一遍是太复杂了,不妨用一个二维数组 v i s [ 3 ] [ n ] vis[3][n] vis[3][n]记录列、主对角线、副对角线是否合法。
int vis[3][N];
int c[N];
void dfs1(int cur,int n){
if(cur==n) ans++;
else for(int i=0;i<n;i++){
if(!vis[0][i]&&!vis[1][cur+i]&&!vis[2][cur-i+n]){
c[cur]=i;//如果单纯求方案数,可以省略c数组
vis[0][i]= vis[1][cur+i]= vis[2][cur-i+n] = 1;
dfs1(cur+1,n);
vis[0][i]= vis[1][cur+i]= vis[2][cur-i+n]=0;
}
}
}
② 位运算法
上面的回溯法,需要用到遍历以前的点或者开一个数组记录,那我们可不可以用别的方法来记录呢?答案是肯定的,下面介绍一下位运算如何求解n皇后问题。
至于位运算不熟悉的伙伴们自行找某度。
解题思路:
仍然是一行一行的扫描,我们用1来表示这一列(col)、主对角线(rd)、副对角线(ld)上已经放了皇后。用二进制来表示这一行的情况。
以四皇后为例,(正方形画的不太好qwq)
当前的情况用二进制表示即为
列(col):0101
主对角线(rd):0010
副对角线(ld):1100
这三个二进制进行或“|”运算,则有
0101
| 0010
| 1100
————————
1111
因为二进制上全是1,即下一行的位置都不合法。如果有0,那么这个位置就是合法位置。譬如:
列(col):0101
主对角线(rd):0001
副对角线(ld):0010
或一下
0101
| 0001
| 0010
————————
0111
那么就把第三行的皇后放在第一列。
现在我们解决了第一个问题,如何记录已经放过的皇后。
我们经过或运算之后,1的地方不合法,但是通常我们用1来表示合法,不妨在上面的基础上取反(~),但是取反之后高位上的0都成了1。我们可以进行下面的操作,就成功把高位还原了。
col= 0101;
rd = 0001;
ld = 0010;
(~(col|rd|ld))&((1<<n)-1)
现在我们面临着的问题是,如在在下一列中选择格子。
我们已经知道了,下一列可以选择的格子(1的格子)位置为p,然后从右边依次选取进行递归。
下一次递归的参数,
列 p | col
主对角线:(rd|p)>>1
副对角线:(ld|p)<<1。
解释一下:
根据图,很显然,下一行col=col|p
主对角线的右下方一定不能放皇后,所以在下一行的时候要右移。
同理,副对角线的左下方一定不能放皇后,所以在下一行的时候要左移。
int ans=0;
void dfs(int n,int row,int col,int ld,int rd){
if(row>=n){
ans++;
return ;
}
int bit =(~(col|ld|rd))&((1<<n)-1);
while(bit>0){
int p = bit&(-bit);//选取最右边的1
dfs(n,row+1,col|p,(ld|p)<<1,(rd|p)>>1);//
bit=bit&(bit-1);//将最右边的1改为0
}
}
参考:
https://blog.csdn.net/kexuanxiu1163/article/details/105304183?