题解 洛谷P1562 【还是N皇后】

  原题:洛谷P1562

  这个题的原理和8皇后的原理是一模一样的,就是必须要用n个皇后把每一个行填满,同时满足每一列,每一行,每一条对角线只有一个棋子。但如果按照原来的方法暴打的话只有60分(优化亲测无效)

  所以这个时候,我们可以用二进制来表示一波状态(可以类比状态压缩的二进制)。从上面的条件来看,我们需要表示的量有:行、列、两条对角线(向左的和向右的),我们用一个状态的某一位的1表示这个状态的这个位置不能放(已经有棋子)

  • 对于每一行:

  我们可以用DFS的深度来减少需要表示状态。(也就是说不用管,见代码)

  • 对于每一列:

  因为在DFS的时候,每一行都必然会放一个棋子,此时需要把这一位的列状态置为1,而且由此可知,当最后放满的时候,表示列的那个状态的必然全是1终止条件

  •  对于向左上的对角线:

  从左上到右下,所以当前这一行影响的应该是下一行的右下一个(↘),然后这里需要注意的是这一行的最后一个的这种对角线是对下一行是没有影响的。举个例子(单就对角线来说):

 这一行: 0 1 1 0 1(1表示有棋子)
 下一行: 0 0 1 1 0


  这个时候可以看出来,这个状态相当于是>>=1。(因为最后一个没有影响所以它被消掉也没有影响)

  •  对于右上的对角线

  从右上到左下,影响下一行的左一个(↙)其他都和向左上的一样,只是这一行的第一个对下一行没有影响。举个例子:

    这一行: 1 1 0 0 1(1表示有棋子)
    下一行: 1 0 0 1 0


  这个时候可以看出来,这个状态相当于是<<=1。(第一个会被移到前面,超出查找范围,会被接下来的运算消掉)

  •  由此可见

  对于这一行来说,可以取的点应该是上一行的所有状态求并集(位或)之后二进制状态位为0的数。举个例子:

    上一行传下来的状态:      列:   4   00100
                       右对角线: 18   10010
                       左对角线:  6   00110
    状态的并集:(这一行可以填充):    22   10110
    那么所以可以放棋子的位置应该是这一行的第2列和第5列

        这个时候,为了方便快速找到并集当中的可行解,联想树状数组的lowbit(),发现只要用1来表示可以放的位置即可,具体方法:并集取反后与全部位置(1~n位)都为1的一个值(all)作与运算(与all作与运算是为了把右对角线多向右移出去的除掉)举个例子:

    ~并集:  0101001(右对角线多出两位)
      all:  0011111
     &all:  0001001

这样只需要每一次取lowbit()之后把状态更新,并将并集减去lowbit()(将这一位置0表示已经取过)后继续DFS。


代码:(如果还没有理解可以用二进制检验函数单步走一下)

 1 #include<cstdio>
 2 #include<iostream>
 3 #include<cstring>
 4 using namespace std;
 5 int n,map[20]={0},all=0,cc=0;//map[i]存放本来就不能放的点,cc表示可行借个数
 6 
 7 //二进制检验函数
 8 int c[20]={0};
 9 void print_in_2(int x){
10     for(int i=0;i<20;i++)c[i]=0;
11     while(x){
12         c[0]++;
13         c[c[0]]=x&1;
14         x>>=1;
15     }
16     for(int i=n;i>0;i--){
17         printf("%d",c[i]);
18     }
19     cout<<endl;
20 }
21 
22 void Init(){
23     scanf("%d",&n);
24     char k[20];//给出的地图
25     for(int i=0;i<n;i++){
26         scanf("%s",k);
27         for(int j=0;j<n;j++){
28             if(k[j]=='.')
29                 map[i]|=(1<<j);//这里是将所给地图左右对称了,易证对称后与原图方案数相同
30         }
31     }
32     all=(1<<n)-1;
33     
34     /*for(int i=0;i<n;i++){
35         print_in_2(map[i]);
36     }*/
37 }
38 int low_bit(int x){//返回第一个1
39     return x&-x;
40 }
41 void DFS(int deep,int line,int l_diag,int r_diag){//深度(行数)和上一行的状态:列、左对角线、右对角线
42     if(line==all){//如果每一列都被填充了,找到答案
43         cc++;
44         return;
45     }
46     //注意:l_d.r_d,line是以1表示已经取过的位置,不是表示还可以放棋子,所以要与all取反
47     int may=all&~(map[deep]|line|l_diag|r_diag);//取可行解,由于map中的1只能影响当前行,所以要在取一次或
48     //print_in_2(may);
49     int v;
50     while(may){
51         v=low_bit(may);
52         may-=v;//将取出的那一位置0
53         //print_in_2(may);
54         DFS(deep+1,line+v,(l_diag+v)>>1,(r_diag+v)<<1);//因为取出的那一位在任意状态中必为0,所以可以直接加上v来把那一位置1;
55     }
56 }
57 void solve(){
58     DFS(0,0,0,0);//我是从0开始存图所以以0为深度开始,其他位置在一开始都可取所以都为0
59     printf("%d",cc);
60 }
61 int main(){
62     Init();
63     solve();
64     return 0;
65 }
原文地址:https://www.cnblogs.com/lazy-people/p/9308472.html