两个NOI题目的启迪8皇后和算24
论出于什么原因和目的,学习C++已经有一个星期左右,从开始就在做NOI的题目,到现在也没有正式的看《Primer C++》,不过还是受益良多,毕竟C++是一种”低级的高级语言“,而且NOI上的题目可以说是循序渐进。不仅仅是从ASM、VB.NET的角度看编程语言,这让我对编程语言语言的理解有了一些深入。
这一篇来记录一下”2.5基本算法之搜索“的两个问题解决的过程。它们有一些类似之处,也有很大不同。
一、8皇后
这是一个非常经典的问题:输出在8*8的棋盘上,摆放8个相互无法吃掉的皇后的全部摆法。一般认为有92种摆法,其中有一些通过镜像、旋转相互重合。因为皇后可以横向、纵向、斜向4个方向吃子且远近不计,所以放上一个皇后之后就会导致一些格子没法再放皇后了:典型的,我们在(0,0)放上一个皇后之后,下次再放就要避开满足x=0,y=0,x=y的格子,这也是简化算法的基础。这导致这个问题求解时,可以按行或按列依次搜索,因为前一行(或列)放置皇后之后就不能再放皇后了。这使得每次遍历都被限制在最多8格之内,从而形成了一颗”枝繁叶疏“的树,可以用回溯法等深度优先算法进行求解。
理论是美好的,实现的过程是残酷的,我用了若干小时编写这份代码,但最终很多理论都没有使用,所以写出来的代码没有什么特别的亮点,甚至自觉很丑陋,所以这篇只有算24的代码。不过在求解过程中,有一些小技巧还是值得和大家共同讨论的:
1、stack进行有序遍历:将y=0的8个位置push,在循环中把y=1-7的push(如果存在可能解),在循环开始输出y=7时的路径,这些路径就是解。
2、使用位棋盘,使用位棋盘可以大为加快记录皇后4向攻击位置(即不能再放皇后的位置)的计算速度:只需要读取mask[i]然后与当前的位棋盘进行按位与运算就得到了放置这个皇后之后的情况。用位棋盘速度非常块也非常节省空间,但是我在进行ULL类型|运算的时候,发现高32位没有被正确操作,可能是我的代码问题,也可能是编译器问题。可以用两个32位来代替,速度也比操作数组快很多。
因为关于8皇后这一经典问题,网上有很多代码,其中有一些质量比较高,很有参考价值,所以在这里就不再赘述(很好,隐藏我的丑陋代码成功)。
二、算24
这个NOI问题是这样的:
总时间限制: 3000ms 内存限制: 65536kB
描述
给出4个小于10个正整数,你可以使用加减乘除4种运算以及括号把这4个数连接起来得到一个表达式。现在的问题是,是否存在一种方式使得得到的表达式的结果等于24。 这里加减乘除以及括号的运算结果和运算的优先级跟我们平常的定义一致(这里的除法定义是实数除法)。 比如,对于5,,,,我们知道5 * ( – / ) = ,因此可以得到24。又比如,对于1,,,,我们怎么都不能得到24。
输入
输入数据包括多行,每行给出一组测试数据,包括4个小于10个正整数。最后一组测试数据中包括4个0,表示输入的结束,这组数据不用处理。
输出
对于每一组测试数据,输出一行,如果可以得到24,输出“YES”;否则,输出“NO”。
样例输入 样例输出
YES
NO
乍一看,好像挺简单……于是,拿过来就写:挨个拿出来和之前的结果运算(开始时把其中1个看作上次结果)、包括颠倒和括号,嘿……只得了5分。然后才想起来仔细分析题目,汗颜啊。这里包括这样一些运算过程:
只表示算式形式,而不代表顺序,逗号通配运算符:
a,b,c,d
(a,b),c,d
a,(b,c),d
a,b,(c,d)
(a,b,c),d
a,(b,c,d)
以及
(a,b),(c,d)
最后一种形式就是第一次5分的问题所在。”一个一个喂“的想法,导致最后一种形式被漏掉了。好吧,老老实实的:每次取2个得到一个计算结果,把计算结果作为一个待选运算数进行迭代:
//递归计算过程
void msearch(double val[],int valcnt){
if(flag){ //找到一种得出24的路径之后不再搜索其他。
return;
}
if(valcnt==){ //当只剩下一个数组元素,说明已经全部合并完成,这个元素就是最终结果。
if(abs(val[]-24.0)<=1e-){ //当结果等于24时,设置全局标记以结束递归。
flag=true;
}
}else{ //当数组元素有两个以上时,进行遍历,两两合并。并且把合并结果写入数组。
int i,j;
for(i=;i<valcnt;i++){ //用两个循环遍历全部的两两组合(参照冒泡法排序)。
for(j=i+;j<valcnt;j++){
//依次进行加减乘除以及减、除操作数的交换
nextval(val,valcnt,i,j,val[i]+val[j]);
nextval(val,valcnt,i,j,val[i]-val[j]);
nextval(val,valcnt,i,j,val[i]*val[j]);
if(val[i]!=){
nextval(val,valcnt,i,j,val[i]/val[j]);
}
nextval(val,valcnt,i,j,val[j]-val[i]);
if(val[j]!=){
nextval(val,valcnt,i,j,val[j]/val[i]);
}
}
}
}
}
这里有一行比较辣眼睛的代码:
if(abs(val[]-24.0)<=1e-){ //当结果等于24时,设置全局标记以结束递归。
如果换成1e-14还是5分……嘿,在我信心满满再次提交的时候,又是5分,于是对识海各种打补丁,结果……还是5分。死马当活马医吧,看看是不是精度问题:根据题意,最小值能到多少呢?3个10,1个1,1/10/10/10……之后进行若干次测试,这个”等于24“的阈值属于区间(0.001,1e-14]。这是动摇我对double精度的认识吗,以前写1e-15都没问题的?希望有大神指点一下。
好像没看见递归,为了在有限的C++知识范围内简化代码,我把它写到了nextval这个过程中,以下是算24的完整代码:
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
bool flag;
void nextval(double val[],int valcnt,int idx1,int idx2,double lastresult);
//递归计算过程
void msearch(double val[],int valcnt){
if(flag){ //找到一种得出24的路径之后不再搜索其他。
return;
}
if(valcnt==){ //当只剩下一个数组元素,说明已经全部合并完成,这个元素就是最终结果。
if(abs(val[]-24.0)<=1e-){ //当结果等于24时,设置全局标记以结束递归。
flag=true;
}
}else{ //当数组元素有两个以上时,进行遍历,两两合并。并且把合并结果写入数组。
int i,j;
for(i=;i<valcnt;i++){ //用两个循环遍历全部的两两组合(参照冒泡法排序)。
for(j=i+;j<valcnt;j++){
//依次进行加减乘除以及减、除操作数的交换
nextval(val,valcnt,i,j,val[i]+val[j]);
nextval(val,valcnt,i,j,val[i]-val[j]);
nextval(val,valcnt,i,j,val[i]*val[j]);
if(val[i]!=){
nextval(val,valcnt,i,j,val[i]/val[j]);
}
nextval(val,valcnt,i,j,val[j]-val[i]);
if(val[j]!=){
nextval(val,valcnt,i,j,val[j]/val[i]);
}
}
}
}
}
//用取出两个数并加入结果的新数组继续递归
void nextval(double val[],int valcnt,int idx1,int idx2,double lastresult){
int i,newvalidx=;
double newval[valcnt-]; //新数组会比原来的少1(两个操作数标为一个结果)
for(i=;i<valcnt;i++){ //把没用到的复制到新数组
if(i!=idx1 && i!=idx2){
newval[newvalidx]=val[i];
newvalidx++;
}
}
newval[newvalidx]=lastresult; //把结果加入新数组
msearch(newval,newvalidx+); //用新数组递归搜索
}
int main(){
double val[];
while(true){
cin>>val[]>>val[]>>val[]>>val[];
if(val[]+val[]+val[]+val[]==){
break;
}
flag=false;
msearch(val,);
if(flag){
cout<<"YES"<<endl;
}else{
cout<<"NO"<<endl;
}
}
}
两个NOI题目的启迪8皇后和算24的更多相关文章
- ES6学习:两个面试题目--关于模板字符串
号称看完就能“让开发飞起来”,不过文中的两个面试题目的知识点并没包括在文中. https://www.jianshu.com/p/287e0bb867ae 文中并没有完整的知识点去完成上面的两道题,这 ...
- 九度oj 题目1254:N皇后问题
题目描述: N皇后问题,即在N*N的方格棋盘内放置了N个皇后,使得它们不相互攻击(即任意2个皇后不允许处在同一排,同一列,也不允许处在同一斜线上.因为皇后可以直走,横走和斜走如下图). 你的任务是,对 ...
- 从一个NOI题目再学习二分查找。
二分法的基本思路是对一个有序序列(递增递减都可以)查找时,测试一个中间下标处的值,若值比期待值小,则在更大的一侧进行查找(反之亦然),查找时再次二分.这比顺序访问要少很多访问量,效率很高. 设:low ...
- 九度oj 题目1140:八皇后
题目描述: 会下国际象棋的人都很清楚:皇后可以在横.竖.斜线上不限步数地吃掉其他棋子.如何将8个皇后放在棋盘上(有8 * 8个方格),使它们谁也不能被吃掉!这就是著名的八皇后问题. 对于某个满足要求的 ...
- 近年NOI题目总结
NOI2015D1T1 题目大意:$T$ 组数据.在一个程序中有无数个变量 $x_i$.现在有 $n$ 条限制,形如 $x_i=x_j$ 或者 $x_i\ne x_j$.(对于每个限制 $i,j$ 给 ...
- 两道SQL题目
1.查询省内所有城市气温都大于35度的省份(表名:Temp) SELECT province FROM Temp WHERE province NOT IN ( SELECT province FRO ...
- Java继承的两道实验题目
设计一个表示二维平面上点的类Point,包含有表示坐标位置的Protect类型的成员变量 获取和设置x和y值的public方法 package classwork_6; public class Po ...
- 【noi 2.5_1789】算24(dfs)
最开始我想的是全排列+枚举符号和括号的方法,但是我自己倒腾了很久还是打不对,只好向他人请教.正解很机智--直接随意将几个数"捆绑"在一起,值存在其中一个数上,其他数标记不可再选,直 ...
- LeetCode:N-Queens I II(n皇后问题)
N-Queens The n-queens puzzle is the problem of placing n queens on an n×n chessboard such that no tw ...
随机推荐
- putty可以远程连接linux,但上不了网(nat模式)
话说,这个问题搞了我一个下午 = = 不过终于可以用NAT模式上网了,还是挺有成就感的 首先放张这样的图上去 证明putty 连linux 是木有问题的,但是上不了网哦~~~ 大家请注意, 当前ip是 ...
- Mybatis #和$的区别
1.#将传入的数据当成一个字符串,会自动加上双引号.如 oder by #{id} ,那么解析后为oder by “id” 2.$对传入的数据不进行操作,直接显示原值.如oder by ${i ...
- Linux与Windows xp操作系统启动过程
Linux启动过程: 第一步,加载BIOS,当你打开计算机电源,计算机会首先加载BIOS信息,BIOS信息是如此的重要,以至于计算机必须在最开始就找到它.这是因为BIOS中包含了CPU的相关信息.设备 ...
- MAC OS terminal 快捷键记录
Command + K 清屏 Command + T 新建标签 Command +W 关闭当前标签页 Command + S 保存终端输出 Command + D 垂直分隔当前标签页 Comma ...
- Pyqt 获取动态生成的QLineEdit值
动态生成控件参考上一篇: http://www.cnblogs.com/dcb3688/p/4588814.html 那如何获取动态生成控件的值呢? 比如,动态的生成10个输入框QLineEdit,输 ...
- 算法系列:FFT 001
转载自http://blog.csdn.net/orbit/article/details/17210461 2012年9月的时候,一个南京的大学生从电视台播放的一段记者采访360总裁周鸿祎的视频中破 ...
- 在WebApi中 集成 Swagger
1. Swagger(俗称:丝袜哥)是什么东西? Swagger 是一个规范和完整的框架,用于生成.描述.调用和可视化 RESTful 风格的 Web 服务.总体目标是使客户端和文件系统作为服务器以同 ...
- JavaScript 富文本编辑器
WEB项目中使用UEditor(富文本编辑器) UEditor - 完整示例 http://ueditor.baidu.com/website/onlinedemo.html UEditor注意事项: ...
- WebForm控件Repeater
我们会发现用拼接字符串来显示一个查询非常的麻烦,有一个控件Repeater帮助你,省去写Foreach LinQ to SQL类 函数类: using System; using System.Col ...
- WPF中的动画——(三)时间线(TimeLine)
WPF中的动画——(三)时间线(TimeLine) 时间线(TimeLine)表示时间段. 它提供的属性可以让控制该时间段的长度.开始时间.重复次数.该时间段内时间进度的快慢等等.在WPF中内置了如下 ...