今天教你如何O2水过八皇后...
题目:
检查一个如下的6 x 6的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。
上面的布局可以用序列2 4 6 1 3 5来描述,第i个数字表示在第i行的相应位置有一个棋子,如下:
行号 1 2 3 4 5 6
列号 2 4 6 1 3 5
这只是跳棋放置的一个解。请编一个程序找出所有跳棋放置的解。并把它们以上面的序列方法输出。解按字典顺序排列。请输出前3个解。最后一行是解的总个数。
这道题为明显的深搜题,具体算法...暴力枚举每一个合法的位置组合,(横)坐标从小到大遍历可保证字典序问题
结合具体代码分析思路:
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; int n; int used[15][15]; int q[15]; int t; int tim; inline void search(int pos){for(int i=1;i<=n;i++){ if(!used[i][pos]){ t++; q[t]=i; if(t==n){ tim++; if(tim<=3){ for(int j=1;j<=n;j++) printf("%d ",q[j]); puts(""); } } else{ for(int j=1;j<=n;j++){ used[j][pos]++; used[i][j]++; } for(int j=max(pos+i-n,1);j<=min(pos+i-1,n);j++) used[j][pos+i-j]++; for(int j=max(pos-i+1,1),t=max(i-pos+1,1);j<=min(pos-i+n,n);j++,t++) used[t][j]++; search(pos+1); for(int j=1;j<=n;j++){ used[j][pos]--; used[i][j]--; } for(int j=max(pos+i-n,1);j<=min(pos+i-1,n);j++) used[j][pos+i-j]--; for(int j=max(pos-i+1,1),t=max(i-pos+1,1);j<=min(pos-i+n,n);j++,t++) used[t][j]--; } t--; } } } int main(){ scanf("%d",&n); search(1); printf("%d",tim); return 0; }
利用used数组保存地图状态,如果不为0即为使用,注意该数组不能用bool,因为多个皇后的势力范围一定会出现覆盖,如果用bool则会在回溯操作时将不该删除的其他皇后势力范围删掉,使用int进行逐层覆盖即可,
关于每个皇后势力范围划分问题,也就是如何根据皇后坐标找对角线的问题,建议画一个图自己去推,很锻炼模拟能力,
至于本代码中pos与i哪个是横坐标哪个是纵坐标...我好像也不知道...在推导皇后势力范围公式时搞反了,导致两变量所处位置与思路中完全不符,然而这并没有关系,因为本题中棋盘为正方形,可以将横纵坐标反过来,也许思路想对横坐标进行搜索枚举,最终也仅是将纵坐标当做结果输出即可
最后,search函数搜索主程序后面的t--必不可少,它是回溯操作的关键,即删去原添加坐标的序号,表示未进行该操作。
再说一下关于时间限制的问题(开O2):
不用inline的时间:
444ms
用了inline的时间:
443ms
(显然inline要快嘛~)
不开O2就会突破天际直奔2000ms
正经考试中这种算法不开O2在该数据范围内仅会WA一个点,还是相当划算的,不过13分没了
6.13update
其实这题不用非常麻烦的赋值语句,把used一个一个赋值,只需开个以为数组并找到合适的标识(比如与y轴的焦点纵坐标)来表示行,列,对角线,就能非常容易地标识使用路径,比无脑赋值效率高太多
至于为什么不写代码...去看6.13的博客去