N皇后问题Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 5670 Accepted Submission(s): 2567
Problem Description
在N*N的方格棋盘放置了N个皇后,使得它们不相互攻击(即任意2个皇后不允许处在同一排,同一列,也不允许处在与棋盘边框成45角的斜线上。
你的任务是,对于给定的N,求出有多少种合法的放置方法。
Input
共有若干行,每行一个正整数N≤10,表示棋盘和皇后的数量;如果N=0,表示结束。
Output
共有若干行,每行一个正整数,表示对应输入行的皇后的不同放置数量。
Sample Input
Sample Output
Author
cgf
Source
Recommend
lcy
|
=====================================算法分析=====================================
这个问题在刘汝佳的白书P123:八皇后问题中讲地已经不能再详细了。
白书上给出了最高效算法,也给出了最高效算法的常规实现代码。
不过对于N<=10的数据规格就算是直接提交这份代码也一样会无奈TLE。
没办法就只好无耻的打表了。
完事却猛然发现最高效算法的位运算实现代码能够直接AC(膜拜)!
代码见下面,理解这份代码需要一定的位运算基础,这里详细解释一下(以N=5时的棋盘规格为例):
///
注:在状态压缩中,通常用( 1 << N ) - 1 来表示最大状态MAXST,用A | B 来合并A和B的状态位,用A & ~B 来清除A
状态中所有的B中的状态位。
///
一、DFS函数参数Col,MainDiag,ContDiag的表示意义:
当整形数Col,MainDiag,ContDiag的第X位为1时。
表示因为之前摆放的皇后的纵向攻击,主对角线斜向攻击,副对角线斜向攻击而使得当前行摆放的皇后不能位于第X列。
-------------------------------------------------------------------------------------------------
#OOOO 当前正在摆放第4行的皇后
OO#OO Col = 1 0 1 0 1
OOOO# MainDiag = 0 0 0 1 1
OOOOO ContDiag = 1 0 0 1 0
OOOOO 图中'O'表示空位,'#'表示皇后
-------------------------------------------------------------------------------------------------
///
二、Col == ( 1 << N ) - 1 的表示意义:
( 1 << N ) - 1 在二进制下就是N个1。
这里Col中的1表示的是因为之前摆放的皇后的纵向攻击而使得当前行摆放的皇后不能位于的列,事实上也就是表示已经摆放了皇
后的列。
那么 Col == ( 1 << N ) - 1 也就表示N列每一列都摆放了皇后,也就是已经摆放了N个皇后,此时即获得一种皇后摆放方案。
///
三、emptycol = ( ( 1 << N ) - 1 ) & ~( Col | MainDiag | ContDiag ) 的表示意义:
首先 Col | MainDiag | ContDiag 的第X位为1时,显然是表示在综合了之前摆放的皇后的所有攻击方式之后当前行摆放的皇后
不能位于第X列。
那么 emptycol = ( ( 1 << N ) - 1 ) & ~( Col | MainDiag | ContDiag ) 的第X位为1时,则是表示当前行摆放的皇后可以位
于第X列。
-------------------------------------------------------------------------------------------------
#OOOO
OO#OO Col | MainDiag | ContDiag = 1 0 1 1 1
OOOO# ( ( 1 << N ) - 1 ) & ~( Col | MainDiag | ContDiag ) = 0 1 0 0 0
OOOOO emptycol = 0 1 0 0 0
OOOOO
-------------------------------------------------------------------------------------------------
///
四、curcol = ( emptycol ) & ( ( ~emptycol ) + 1 ) 的表示意义:
emptycol & ~emptycol 的结果显然是0,但是~emptycol一个巧妙的+1之后却让结果极富意义。
首先,2进制数字的+1进位(类比一下10进制)还是不难理解的:
从最后一位开始,若当前位数字为1则将其进位为0并继续向前进位,若当前位数字为0则将其进位为1并停止进位。
现假设emptycol中的第一个1在第First位,而其后First-1位都是0。
那么~emptycol中的第一个0在第First位,而其后First-1位都是1。
当~emptycol进行+1进位时,其后First-1位全部进位为0,其第First进位为1,而其First之前的所有位则不变。
这时候再将emptycol与其进行&运算,显然答案的后First-1位为0,第First位为1,而First之前的所有位为0。
也就是说,curcol = ( emptycol ) & ( ( ~emptycol ) + 1 ) 中仅有一个1,而且其位置即emptycol中最后一个1的位置!
那么curcol的表示意义即为当前行摆放的皇后可以摆放的最后一列。
-------------------------------------------------------------------------------------------------
#OOOO emptycol = 0 1 0 0 0
OO#OO ~emptycol = 1 0 1 1 1
OOOO# ~emptycol + 1 = 1 1 0 0 0
OOOOO ( emptycol ) & ( ( ~emptycol ) + 1 ) = 0 1 0 0 0
OOOOO curcol = 0 1 0 0 0
-------------------------------------------------------------------------------------------------
///
五、 emptycol &= ~curcol 的表示意义:
清除emptycol的最后一个1,表示当前已将皇后摆放在可以摆放的最后一列,之后不必再对此情况进行DFS。
因而while的循环结束条件是emptycol为0,也就是emptycol中没有1,表示已经没有需要DFS的情况了。
///
六、DFS递归的函数参数 Col | curcol,( MainDiag | curcol ) >> 1,( ContDiag | curcol ) << 1 的表示意义:
1、Col中的1表示因为之前摆放的皇后的纵向攻击而使得当前行摆放的皇后不能位于的列,而下一行的皇后显然也是不能摆放在
这些列的。
curcol中唯一的1表示当前行皇后摆放的列,那么下一行的皇后因为当前行皇后的纵向攻击而不能摆放在这一列。
所以 Col | curcol 即下一行的皇后因为其之前摆放的皇后的纵向攻击而不能摆放的列。
2、MainDiag中的1表示因为之前摆放的皇后的主对角线斜向攻击而使得当前行摆放的皇后不能位于的列。
由于攻击是沿主对角线的,故而当前行影响的是第X列的话,那下一行影响的就是第X+1列。
因此之前摆放的皇后的主对角线斜向攻击传递到下一行时则将变为 MainDiag >> 1。
而因为当前行摆放状态为curcol的皇后的主对角线斜向攻击,下一行摆放的皇后将不能摆放在curcol >> 1 中唯一的1的所
在位置对应的列。
所以 MainDiag >>1 | curcol >> 1 也就是( MainDiag | curcol ) >> 1 即下一行的皇后因为其之前摆放的皇后的主对角
线斜向攻击而不能摆放的列。
3、( ContDiag | curcol ) << 1 的表示意义可类比第2点。
///
七、只要理解了白书上八皇后问题的算法,看看位运算,自己画一画,下面的代码还是很容易看懂的。
======================================代码========================================
#include<stdio.h>
int N,SchemeNum;
void DFS(int Col,int MainDiag,int ContDiag)
{
if(Col==(1<<N)-1) { ++SchemeNum; return; }
int emptycol=(((1<<N)-1)&~(Col|MainDiag|ContDiag));
while(emptycol)
{
int curcol=(emptycol)&((~emptycol)+1);
emptycol&=~curcol;
DFS(Col|curcol,(MainDiag|curcol)>>1,(ContDiag|curcol)<<1);
}
}
int main()
{
while(scanf("%d",&N)==1&&N)
{
SchemeNum=0;
DFS(0,0,0);
printf("%d\n",SchemeNum);
}
return 0;
}