在蓝桥杯基础训练题中,出现这样一道题目:算法
问题描述数组
给定一个n*n的棋盘,棋盘中有一些位置不能放皇后。如今要向棋盘中放入n个黑皇后和n个白皇后,使任意的两个黑皇后都不在同一行、同一列或同一条对角线上,任意的两个白皇后都不在同一行、同一列或同一条对角线上。问总共有多少种放法?n小于等于8。数据结构
输入格式ide
输入的第一行为一个整数n,表示棋盘的大小。
接下来n行,每行n个0或1的整数,若是一个整数为1,表示对应的位置能够放皇后,若是一个整数为0,表示对应的位置不能够放皇后。函数
输出格式学习
输出一个整数,表示总共有多少种放法。spa
样例输入.net
4
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1orm
样例输出blog
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皇后问题比此多了两个限制条件:
一、n*n的棋盘中有黑皇后和白皇后各n个,任意两个同色皇后不能在同一行、同一列或同一条对角线上,并且同一位置只有一个皇后;
二、棋盘中有数个位置不能听任何皇后(个数和位置随机);
条件还不算苛刻,由目前公认N皇后的最高效算法稍微改造一下即可以解决这题。
至此,你们可能会有两个疑问:
一、在每行中,若是两种皇后可放位置的首选位置冲突时如何解决?可否保证两种皇后分别放在此位置的状况都统计上?
二、如何筛选掉条件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行中的这个位置不能放皇后。