在蓝桥杯基础训练题中,出现这样一道题目:
问题描述
给定一个n*n的棋盘,棋盘中有一些位置不能放皇后。现在要向棋盘中放入n个黑皇后和n个白皇后,使任意的两个黑皇后都不在同一行、同一列或同一条对角线上,任意的两个白皇后都不在同一行、同一列或同一条对角线上。问总共有多少种放法?n小于等于8。
输入格式
输入的第一行为一个整数n,表示棋盘的大小。
接下来n行,每行n个0或1的整数,如果一个整数为1,表示对应的位置可以放皇后,如果一个整数为0,表示对应的位置不可以放皇后。
输出格式
输出一个整数,表示总共有多少种放法。
样例输入
4
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1
样例输出
2
样例输入
4
1 0 1 1
1 1 1 1
1 1 1 1
1 1 1 1
样例输出
0
在解决2n皇后问题前,先来学习目前公认N皇后的最高效算法。
使用位运算来求解N皇后的高效算法
核心代码如下:
void test(int row, int ld, int rd) { int pos, p; if ( row != upperlim ) { pos = upperlim & (~(row | ld | rd )); while ( pos ) { p = pos & (~pos + 1); pos = pos - p; test(row | p, (ld | p) << 1, (rd | p) >> 1); } } else ++Ans; }
初始化: upperlim = (1 << n)-1; Ans = 0;
调用参数:test(0, 0, 0);
和普通算法一样,这是一个递归函数,程序一行一行地寻找可以放皇后的地方。函数带三个参数row、ld和rd,分别表示在纵列和两个对角线方向的限制条件下这一行的哪些地方不能放。位于该行上的冲突位置就用row、ld和rd中的1来表示。把它们三个并起来,得到该行所有的禁位,取反后就得到所有可以放的位置(用pos来表示)。
p = pos & (~pos + 1)其结果是取出最右边的那个1。这样,p就表示该行的某个可以放子的位置,把它从pos中移除并递归调用test过程。
注意递归调用时三个参数的变化,每个参数都加上了一个禁位,但两个对角线方向的禁位对下一行的影响需要平移一位。最后,如果递归到某个时候发现row=upperlim了,说明n个皇后全放进去了,找到的解的个数加一。
注:
upperlime:=(1 << n)-1 就生成了n个1组成的二进制数。
这个程序是从上向下搜索的。
pos & -pos 的意思就是取最右边的 1 再组成二进制数,相当于 pos &(~pos +1),因为取反以后刚好所有数都是相反的(怎么听着像废话),再加 1 ,就是改变最低位,如果低位的几个数都是1,加的这个 1 就会进上去,一直进到 0 ,在做与运算就和原数对应的 1 重合了。举例可以说明:
原数 0 0 0 0 1 0 0 0 原数 0 1 0 1 0 0 1 1
取反 1 1 1 1 0 1 1 1 取反 1 0 1 0 1 1 0 0
加1 1 1 1 1 1 0 0 0 加1 1 0 1 0 1 1 0 1
与运算 0 0 0 0 1 0 0 0 and 0 0 0 0 0 0 0 1
其中呢,这个取反再加 1 就是补码,and 运算 与负数,就是按位和补码与运算。
(ld | p)<< 1 是因为由ld造成的占位在下一行要右移一下;
(rd | p)>> 1 是因为由rd造成的占位在下一行要左移一下。
ld rd row 还要和upperlime 与运算 一下,这样做的结果就是从最低位数起取n个数为有效位置,原因是在上一次的运算中ld发生了右移,如果不and的话,就会误把n以外的位置当做有效位。
pos 已经完成任务了还要减去p 是因为?
while 循环是因为?
在进行到某一层的搜索时,pos中存储了所有的可放位置,为了求出所有解,必须遍历所有可放的位置,而每走过一个点必须要删掉它,否则就成死循环啦!
这个是目前公认N皇后的最高效算法。
(以上内容来源于博客http://blog.csdn.net/hackbuteer1/article/details/6657109)
这个算法如此巧妙地解决了n皇后问题。不过,2n皇后问题比此多了两个限制条件:
1、n*n的棋盘中有黑皇后和白皇后各n个,任意两个同色皇后不能在同一行、同一列或同一条对角线上,而且同一位置只有一个皇后;
2、棋盘中有数个位置不能放任何皇后(个数和位置随机);
条件还不算苛刻,由目前公认N皇后的最高效算法稍微改造一下便可以解决这题。
至此,大家可能会有两个疑问:
1、在每行中,如果两种皇后可放位置的首选位置冲突时如何解决?能否保证两种皇后分别放在此位置的情况都统计上?
2、如何筛选掉条件2中的这些禁止位?
/* ** 目前最快的2N皇后递归解决方法 ** 2N Queens Problem ** 试探-回溯算法,递归实现 ** 根据http://blog.csdn.net/hackbuteer1/article/details/6657109改编 */ #include <stdio.h> #include <stdlib.h> #define MAXN 32 long sum = 0, upperlim = 1, wall[MAXN] = {0}; // 试探算法从最右边的列开始。 void BlackWhiteQueen(int line, long row1, long ld1, long rd1, long row2, long ld2, long rd2) { long pos1, pos2, p1, p2; if(row1 != upperlim || row2 != upperlim) { // row,ld,rd进行“或”运算,求得所有可以放置皇后的列,对应位为0, // 然后再取反后“与”上全1的数,来求得当前所有可以放置皇后的位置,对应列改为1 // 也就是求取当前哪些列可以放置皇后 pos1 = upperlim & ~(row1 | ld1 | rd1) & ~wall[line]; while(pos1) // 0 -- 皇后没有地方可放,回溯 { // 拷贝pos最右边为1的bit,其余bit置0 // 也就是取得可以放皇后的最右边的列 p1 = pos1 & -pos1; // 将pos最右边为1的bit清零 // 也就是为获取下一次的最右可用列使用做准备, // 程序将来会回溯到这个位置继续试探 pos1 -= p1; pos2 = upperlim & ~(row2 | ld2 | rd2) & ~wall[line] & ~p1; while(pos2) { p2 = pos2 & -pos2; pos2 -= p2; // row + p,将当前列置1,表示记录这次皇后放置的列。 // (ld + p) << 1,标记当前皇后左边相邻的列不允许下一个皇后放置。 // (ld + p) >> 1,标记当前皇后右边相邻的列不允许下一个皇后放置。 // 此处的移位操作实际上是记录对角线上的限制,只是因为问题都化归 // 到一行网格上来解决,所以表示为列的限制就可以了。显然,随着移位 // 在每次选择列之前进行,原来N×N网格中某个已放置的皇后针对其对角线 // 上产生的限制都被记录下来了 BlackWhiteQueen(line + 1, row1 + p1, (ld1 + p1) << 1, (rd1 + p1) >> 1, row2 + p2, (ld2 + p2) << 1, (rd2 + p2) >> 1); } } } else sum++; }//BlackWhiteQueen int main(int argc, char const *argv[]) { int n = 8, i, d; scanf("%d", &n); // 因为整型数的限制,最大只能32位, // 如果想处理N大于32的皇后问题,需要 // 用bitset数据结构进行存储 if((n < 1) || (n > 32)) { printf("只能计算1~32之间\n"); exit(-1); } // N个皇后只需N位存储,N列中某列有皇后则对应bit置1。 upperlim = (upperlim << n) - 1; for (i = 0; i < n * n; ++i) { scanf("%d", &d); if(d == 0) wall[i / n] |= 1 << (i % n); } BlackWhiteQueen(0, 0, 0, 0, 0, 0, 0); printf("%d", sum); return 0; }
疑问一对于正确的回溯算法来说,完全不在话下。
需要注意的是,当计算出当前行白皇后所放位置p1后,计算p2时
pos2 = upperlim & ~(row2 | ld2 | rd2) & ~wall[line] & ~p1;
不能写成
pos2 = (upperlim & ~(row2 | ld2 | rd2) & ~wall[line]) - p1;//或pos2 = (upperlim & ~(row2 | ld2 | rd2)) - wall[line] - p1;
当两种皇后可放位置的首选位置不同时,后者得出的pos2显然是错误的。
之所以pos1 -= p1;正确是因为p1中为‘1’的位pos1对应的位也为‘1’。而pos2却不一定。
而对于问题2,在这里参考了row, ld, rd这三个参数的做法,wall[]数组中,前n个有效,wall[k]的二进制数中为‘1’的位表示第k行中的这个位置不能放皇后。