Beyond the Void
BYVoid
USACO 1.5.4 Checker Challenge (N皇后问题) 位运算解法

经典的N皇后(8皇后扩展)问题,学过算法的都知道。

这道题要用回溯法,搜索+优化,但一般的方法很难解决N>=13以上的问题。所以这里重点介绍位操作法。

什么是位运算? 程 序 中的所有数在计算机内存中都是以二进制的形式储存的。位运算说穿了,就是直接对整数在内存中的二进制位进行操作。比如,and运算本来是一个逻辑运 算 符,但整数与整数之间也可以进行and运算。举个例子,6的二进制是110,11的二进制是1011,那么6 and 11的结果就是2,它是二进制 对应位进行逻辑运算的结果(0表示False,1表示True,空位都当0处理): 110 AND 1011

0010 –> 2 由于位运算直接对内存数据进行操作,不需要转成十进制,因此处理速度非常快。

我的解法基本思想还是用回溯法,和普通算法一样,这是一个递归过程,程序一行一行地寻找可以放皇后的地方。过程带三个参数,row、ld和rd,分别表示在 纵列和两个对角线方向的限制条件下这一行的哪些地方不能放。我们以6x6的棋盘为例,看看程序是怎么工作的。假设现在已经递归到第四层,前三层放的子已经 标在左图上了。红色、蓝色和绿色的线分别表示三个方向上有冲突的位置,位于该行上的冲突位置就用row、ld和rd中的1来表示。

upperlim=(1 << n)-1意为算出该棋盘的目标状态(全部被占满)。

pos=upperlim & ~(row | ld | rd)

把它们三个并起来,得到该行所有的禁位,取反后就得到所有可以放的位置(用pos来表示)。

p=pos & -pos;

-a相当于~a + 1,这里的代码就相当于pos & (~pos+1),其结果是取出最右边的那个1。

这样,p就表示该行的某个可以放子的位置,把它从pos中移除并递归调用test过程。

注意递归调用时三个参数的变化,每个参数都加上了一个禁位,但两个对角线方向的禁位对下一行的影响需要平移一位。

最后,如果递归到某个时候发现row=upperlim (目标状态111111) 了,说明六个皇后全放进去了,此时程序找到的解的个数加1。

代码如下:

/*
ID: cmykrgb1
PROG: checker
LANG: C++
*/
#include <stdio.h>
FILE *fi,*fo;
unsigned long int upperlim,sum;
int a[14],n;

int ps(int a) //不用 log 节省时间
{
	switch (a)
	{
	case 1:
		return 1;
	case 2:
		return 2;
	case 4:
		return 3;
	case 8:
		return 4;
	case 16:
		return 5;
	case 32:
		return 6;
	case 64:
		return 7;
	case 128:
		return 8;
	case 256:
		return 9;
	case 512:
		return 10;
	case 1024:
		return 11;
	case 2048:
		return 12;
	case 4096:
		return 13;
	}
}

void test(unsigned long int row,unsigned long int ld,unsigned long int rd,int deep)
{
	unsigned long int pos,p;
	if (row!=upperlim)
	{
     pos=upperlim & ~(row | ld | rd);
     while (pos!=0) 
     {
        p=pos & -pos;
        pos=pos-p;
		if (sum<3) a[deep]=p;
        test(row+p,(ld+p)<< 1,(rd+p)>> 1,deep+1);
	 }
	}
 else
{
	sum++;
	int i;
	if (sum<=3) 
	{
		for (i=1;i<=n-1;i++)
			fprintf(fo,"%d ",ps(a[i]));
		fprintf(fo,"%dn",ps(a[n]));
	}
}

}

int main(void)
{
	fi=fopen("checker.in","r");
	fo=fopen("checker.out","w");
	fscanf(fi,"%d",&n);
	fclose(fi);
	upperlim=(1 << n)-1;
	test(0,0,0,1);
	fprintf(fo,"%dn",sum);
	fclose(fo);
	return 0;
}

上次修改时间 2017-05-26

相关日志