N皇后的位运算有感

N皇后很明显是一个NP—Hard问题,如果n足够大的话,在有限较短的时间内是很难得出答案的,但是注意到N皇后(笔者认为这类问题称为棋盘问题更为贴切),在n*n棋盘之上,每个点有且只有两种状态,这与电脑自身的进制非常类似,因此很自然的想到状态压缩,通过二进制码来表示原先通过数组来表示的皇后状态,也就是我们从原先的二维问题转换为一维问题
这边补充一些前置知识:
神奇的位运算:
kk & (kk-1) : 可以将kk的二进制码的最右边的1去掉
eg: 10010 & (10010-1) = 10010 & 10001 = 10000

kk & -kk : 可以将kk最左边的1提取出来
eg: 10010 & -10010 = 10010 & 01110 = 00010
这边熟悉补码的读者应该非常熟悉在计算机中,负数的补码的第二种算法是从右往左直到第一个1为止都与原码相通后面的则与原码相反
即100 10的补码是011 10,所以这个位运算可以通过这个定义方式来帮助理解

~kk : 可以将kk的值反转
eg: ~10010 = 01101

那么这样子我们就可以通过vis,L,R这三个二进制码来分别表示,列,副对角线,主对角线的情况,笔者会在下面逐渐讲出
熟悉N皇后的读者应该直到,N皇后其实可以从原先的n*n的子序列,降级为1-n的列排序,因此行在这边仅作为终止标志
vis是记录列的皇后情况我们使用1表示有皇后,即1001表示第一列和第四列有皇后
L和R的实现方法类似,因此不失一般性的,笔者就之讲解L的代表含义
L表示的是副对角线的情况,这边可以这样考虑如果是1,那么该列就在前面皇后的副对角线上
这边我们考虑当前层是cur层,我们已经确定insert=00010是合法的皇后位置,此时L=01010
那么此时我们要如此操作:
L = insert | L; 这边表示的含义是将当前层合法的皇后位置加入L中
L = L << 1; 想一想,在cur+1层,L的影响是不是上一层L的影响总体都往左移动一位
注意下面0表示空,1表示皇后,2表示皇后的副对角线影响位置
cur :02010 (此时L=01010)
cur+1:20200 (此时L=10100)

最后就是对前面神奇的位运算的收尾了:
很明显vis|L|R中含有1的位置都是非法的皇后位置,即此时合法填充的皇后位置该处应为0
我们不妨用valid来表示当前皇后可以放置的位置
那么很自然的想到valid = ~(vis|L|R)
其实就是不可以的补集就是可行集
当然仅仅是这样仍然是不行的,因为我们使用的整数类型未必都是n位,因此前面会多出许多1,所以我们还需要base = (1<<n) - 1;来维护n位的问题
所以valid = base & !(vis|L|R)

那么接下来通过老朋友dfs的组装就可以见到全新版本的位运算n皇后,只能说位运算真的很神奇

点击查看笔者代码
void dfs(int cur, int vis, int L, int R) {
  if(cur == n) { cnt++; return; }
  int temp = base&~(vis | L | R);
  while(temp) {
  	int insert = temp & -temp;
  	temp = temp ^ insert;//这行等价于temp = temp & (temp-1),想想为什么
  	dfs(cur+1, vis|insert, (L | insert) << 1, (R | insert) >> 1);
  }
}
posted @   banyanrong  阅读(93)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示