皇后问题
题目描述:
有一个n*n的棋盘,你要在这个棋盘上放上n个皇后,使得她们不能相互攻击。当然为了使得问题更加有趣,我们在棋盘上限定了一些位置使得这些位置上不能放皇后。
输入格式:
第一行两个数n和m ,表示在一个n*n的棋盘上放n个皇后,有m个受限位置。
接下来m行每行两个数,x和y,表示第x行,第y列这个位置不可以放皇后。
输出格式:
一行一个数 ans,表示总的方案数。
样例输入:
4 1 1 2
样例输出:
1 朴素算法(60分)
#include<iostream> using namespace std; int n,m,b[20][20],l[20],pi[40],na[40],x,y,ans; int DFS(int i){ if(i==n+1){ans++;return 0;} for(int j=1;j<=n;j++){ if(b[i][j]==0 && l[j]==0 && pi[i+j]==0 && na[i-j+n]==0){ l[j]=1; pi[i+j]=1; na[i-j+n]=1; DFS(i+1); l[j]=0; pi[i+j]=0; na[i-j+n]=0; } } return 0; } int main() { cin>>n>>m; for(int i=1;i<=m;i++){ cin>>x>>y; b[x][y]=1; } DFS(1); cout<<ans<<endl; }
高级算法(位运算)(100分)
#include<iostream> using namespace std; int n,m,x,y,ans,a[20]; void DFS(int row,int ld,int rd,int h) { if(row!=(1 << n) - 1){ int pos=((1<<n)-1)&~(row|ld|rd|a[h]); while(pos){ int p=pos & -pos; pos-=p; DFS(row+p,(ld+p)<<1,(rd+p)>>1,h+1); } } else ans++; } int main() { cin>>n>>m; for(int i=1;i<=m;i++){ cin>>x>>y; a[x]+=1<<(y-1); } DFS(0,0,0,1); cout<<ans<<endl; }
高级算法(位运算)思路:
初始化: 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以外的位置当做有效位。