两个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 ...
随机推荐
- javascript小技巧
事件源对象 event.srcElement.tagName event.srcElement.type 捕获释放 event.srcElement.setCapture(); event.srcE ...
- Websocket通讯简析
什么是Websocket Websocket是一种全新的协议,不属于HTTP无状态协议,协议名为"ws",这意味着一个Websocket连接地址会是这样的写法:ws://**.We ...
- Spring + Jedis集成Redis(单例redis数据库)
这几天没事,就把之前学习的redis代码整理一遍,废话不多说,上步骤. 1.pom.xml引入资源: <dependency> <groupId>org.springframe ...
- (一)洞悉linux下的Netfilter&iptables:什么是Netfilter?
转自:http://blog.chinaunix.net/uid-23069658-id-3160506.html 本人研究linux的防火墙系统也有一段时间了,由于近来涉及到的工作比较纷杂,久而久之 ...
- 创建好Android Application Project 后运行就报错。
如图: 这个问题有可能是有可能是没导入Android support库,简单了解一下: google提供了Android Support Library package 系列的包来保证来高版本sdk开 ...
- one_person年轻的程序员
回顾大学三年,通过良师的教导和自身的刻苦学习,我已初步掌握如何运用计算机编程,也养成了认真对待学习和工作的好习惯! 在思想品德上,本人有良好道德修养,并有坚定的政治方向.我热爱祖国,热爱人民,遵纪守法 ...
- fork()创建子进程
fork()系统调用是Unix下以自身进程创建子进程的系统调用,一次调用,两次返回,如果返回是0,则是子进程,如果返回值>0,则是父进程(返回值是子进程的pid) 在fork()的调用处,整个父 ...
- JBD日志的定位、分析和恢复
在上一篇中,我们介绍了Ext3文件系统的日志可以看做一个文件,由JBD进行管理.自然而然引出如下这些问题: 1)如何定位ext3日志文件和查看日志文件的裸数据? 2)ext3日志文件数据在物理上是如何 ...
- js学习
2014-02-21 var p=function(){}(); //表示定义一个变量P,变量后面的函数为返回值 var p = function(){return 'abc';}(); alert( ...
- 用户 'IIS APPPOOL\DefaultAppPool' 登录失败。
今天新建了一个ASP.NET(Language=C#)网站,配置好数据库后编写了几行代码测试数据库的是否能正常使用. 当运行程序时,第一个页面都没有打开就出现了错误(因为我首页就访问数据库,填充一些D ...