南师附中集训总结Day3

来源:互联网 发布:unity3d 向目标移动 编辑:程序博客网 时间:2024/04/29 15:22

南师附中集训总结Day3

1.N皇后问题

题目描述:在一个N*N的棋盘上放着N个皇后,要求这些皇后任意两个都不在同一行,同一列,同一对角线上。输入一个正整数N(N>=4),输出所有可能的皇后放置方案数。

题解:一道经典的回溯问题。使用一个函数find(i),i代表当前皇后所在的层数,因为每层只可能有一个皇后,所以这么设置函数可以简化程序。另外设置三个数组lie[],ld[],rd[],分别代表列,左对角线,右对角线,此处有一个小技巧,在同一左对角线上的点,如a[i][j],i+j的值为定值;在同一右对角线上的点,i-j的值为定值,也就是说,如果想要模拟某一左对角线上已经有皇后了,只需要把对应的ld[i+j]的值改为1(初始值为0,代表此对角线上无皇后)就可以标记该对角线上已有皇后了!每次调用find,就把第i行从左到右扫描一遍,对这一行上的每一点判断能否放置一个皇后(条件是lie[j]==0 && ld[i+j]==0 && rd[i-j+n]==0),如果可以放,就先放上,标记这一列,左对角线,右对角线上已有皇后(lie[j]=1;ld[i+j]=1;rd[i-j+n]=1;),之后find(i+1),在find(i+1)之后,关键的回溯步骤是,重新把这一列,左对角线,右对角线标记为不存在皇后(lie[j]=0; ld[i+j]=0; rd[i-j+n]=0;),就可以perfectly solve this question。

  至于为什么在rd[i-j+n]里加上n,是为了适应c++里数组下表从0开始的特征,不然i-j是会出现负数的。

  该算法的缺点是...复杂度太高,n=15,就要好久才能出结果了。

#include<cstdio>#include<iostream>#include<algorithm>#include<cstring>using namespace std;int n,sum=0,lie[1000],ld[1000],rd[1000];//0为可以放入,1为不可放入。 rd:1--2*n-1,ld:2--2*n //a[1000][1000],int find(int i){if (i>n) sum++;else{for (int j=1;j<=n;j++){if (lie[j]==0 && ld[i+j]==0 && rd[i-j+n]==0){   lie[j]=1;   ld[i+j]=1;   rd[i-j+n]=1;      find(i+1);      lie[j]=0;   ld[i+j]=0;   rd[i-j+n]=0;    }    }    }    return 0;} int main(){cin>>n;memset(lie,0,sizeof(lie));memset(ld,0,sizeof(ld));memset(rd,0,sizeof(rd));find(1);cout<<sum;return 0;} 

2.N皇后位运算优化


  位运算实在是个实用的玩意儿。接着上一题的思路,从上至下处理每一行。不过需要改动find函数的参数,这次需要三个参数,lie,ld,rd,分别代表对于同一行,哪些列被占用了,哪些左对角线被占用了,哪些右对角线被占用了(0代表未被占用,1代表被占用)。

  每次调用find函数,先把lie,ld,rd合并(也就是用或运算“|”,例如lie=00001,ld=00010,rd=00100,结果就是p=00111),然后把p取反(变为11000),并用已经算好的u固定p的位数(例如n=4,那么u=1111,此时仅需要4位二进制数,p变为1000,固定的过程用 与运算“&”完成)。

  最后用while循环从右向左扫描p中剩下的每一位“1”,如果该位上是“1”,则表示可以放棋子。注意,pos=p & -p的功能就是定位到pos从右向左的的一个“1”,pos的值就是这一位上的“1”代表的十进制数(请原谅我不能解释为什么..)。从p减去pos,相当于把pos所在的位上的“1”赋值为“0”,作用是方便下一次循环时能找到下一个“1”。最后调用find(lie | pos,(ld | pos)<<1,(rd | pos)>>1),也就是模拟出下一行的情况,把pos放到lie,ld,rd中,并将ld左移一位,将rd右移一位。

(point1:可以想象对于同一根左对角线,如果在某行的情况是0010,那么到了下一行,由于对角线是斜的(废话),那么下一行上,不可以放置皇后的位置,也就是1的位置,相对于上一行会左移一格,变为0100,最好可以手画一个棋盘观察一下。这就是要把ld,rd分别左移右移的原因)

(point2:在lie,ld,rd中,0代表未被占用,1代表被占用,而在p中,规则却恰好相反。这是为了操作的方便:使用pos=p & -p可以找到最后一位1,如此操作,pos又恰好可以直接和lie,ld,rd进行或运算,本来在p中表示未被占用的“1”可以直接放到lie中表示已被占用(本来就是要在这一行上放上一枚棋子嘛),简化了代码,不过使代码更难理解

(point3:位运算就不单独解释了。)


#include<cstdio>#include<cstring>#include<algorithm>#include<iostream>using namespace std;int n=0,sum=0,u=1;int find(int lie,int ld,int rd){if (lie == u) sum++;    else{   int p,pos=1;  p=lie | ld | rd;  p=(~p) & u;  pos=p & -p;  while (pos!=0){    pos=p & -p;    p=p-pos;  if (pos!=0)find(lie | pos,(ld | pos)>>1,(rd | pos)<<1);  }    }}int main(){cin>>n;for (int i=1;i<=n;i++){u=u*2;}u=u-1;find(0,0,0);cout<<sum;return 0;} 



原创粉丝点击