【BIT2021程设】2. 解谜游戏——初见DFS
写在前面:
本系列博客仅作为本人十一假期过于无聊的产物,对小学期的程序设计作业进行一个总结式的回顾,如果将来有BIT的学弟学妹们在百度搜思路时翻到了这一条博客,也希望它能对你产生一点帮助(当然,依经验来看,每年的题目也会有些许的不同,所以不能保证每一题都覆盖到,还请见谅)。
不过本人由于学艺不精,代码定有许多不足之处,欢迎各位一同来探讨。
同时请未来浏览这条博客的学弟学妹们注意,对于我给出完整代码的这些题,仅作帮助大家理解思路所用(当然,因为懒,所以大部分题我都只给一个伪代码)。Anyway,请勿直接复制黏贴代码,小学期的作业也是要查重的,一旦被查到代码重复会严厉扣分,最好的方法是浏览一遍代码并且掌握相关的要领后自己手打一遍,同时也要做好总结和回顾的工作,这样才能高效地提升自己的代码水平。
加油!
成绩 | 10 | 开启时间 | 2021年08月24日 星期二 09:00 |
折扣 | 0.8 | 折扣时间 | 2021年08月27日 星期五 23:00 |
允许迟交 | 否 | 关闭时间 | 2021年10月10日 星期日 23:00 |
Description
小张是一个密室逃脱爱好者,在密室逃脱的游戏中,你需要解开一系列谜题最终拿到出门的密码。现在小张需要打开一个藏有线索的箱子,但箱子上有下图所示的密码锁。
每个点是一个按钮,每个按钮里面有一个小灯。如上图,红色代表灯亮,白色代表灯灭。每当按下按钮,此按钮的灯以及其上下左右四个方向按钮的灯状态会改变(如果原来灯亮则灯灭,如果原来灯灭则灯亮)。如果小张通过按按钮将灯全部熄灭则能可以打开箱子。
对于这个密码锁,我们可以先按下左上角的按钮,密码锁状态变为下图。
再按下右下角的按钮,密码锁状态变为下图。
最后按下中间的按钮,灯全部熄灭。
现在小张给你一些密码锁的状态,请你告诉他最少按几次按钮能够把灯全部熄灭。
Input
第一行两个整数。
接下来行,每行一个长度为的01字符串,0表示灯初始状态灭,1表示灯初始状态亮。
Output
一行一个整数,表示最少按几次按钮可以把灯全部熄灭。
Notes
第一个样例见题目描述,第二个样例按左上和右下两个按钮。
测试用例保证一定有解。
题意分析:
看到这题我觉得多数人的第一想法是尝试从结果反推,看看这个问题是不是P问题(名词解释:NP问题、P问题),然后撞了无数次南墙(埋一个伏笔)。但实际上看到这个数据规模我们大抵也能猜到这道题的解法应该相对比较暴力,所以在粗略地分析这道问题很难有P解法之后,应该转向考虑暴力解法。
考虑暴力解法之前,先研究一下暴力的入口在哪。首先不难发现,每个灯的有效操作就是“按一次”或者“不按”,如果按两次以上,那和前两种操作没有区别,我们可以不进行考虑;其次,最终的结果与按的顺序无关,所以我们可以从前至后地来进行遍历,暴力求解。
然而暴力解法的问题在于,如果我们逐个地进行遍历,每个灯有两种状态,至多有个灯,于是最多需要遍历次,可能多数学生对这样的数字没有概念,但做多了题目就会知道这样的做法显然是我们不能接受的,因此我们就需要考虑进行剪枝操作(名词解释:剪枝)。
我们先从最简单的一行的情况开始看,那么总共需要遍历的次数就是,这个数字看着就和蔼可亲多了。我们可以发现,在第一行遍历完之后,第一行的某个灯的亮灭情况只能由位于其正下方的第二行的那个灯来控制了(因为只有他的上下左右和自己可以改变他的亮灭情况),那么为了达到灭掉所有灯的最终结果,我们对第二行的操作是唯一的,即对每一个第一行亮着的灯,按下位于其正下方的第二行的灯,其他灯保持不变,如果不这么操作,就无法熄灭第一行所有灯,这种情况就必然失败。我们还可以发现,在这样之后,第二行的灯的亮灭情况也仅能由第三行控制了……如此循环往复,直到按下最后一行的灯以熄灭倒数第二行的灯,此时最后一行的灯已经不能被任何灯控制了(因为他们的下方已经没有灯了),此时最后一行如果还有亮着的灯,说明此路不通,那么一定是第一行遍历到的这种情况是不可行的。
那么现在我们就已经把所有的步骤简化为了:遍历第一行的所有情况、根据遍历出的情况确定2~n行每一行的操作、所有操作结束后检测第n行是否全灭。
所以遍历要如何进行呢?在这提供两种思路:
先讲第零种,并不合理的思路,即进行至多十六层嵌套循环,每层循环两个值。这个做法的不合理之处,一是代码量太大,属于面向肝和面向脑容量编程,二是DEBUG和维护起来太麻烦,三是灵活性不够,万一哪天老板需要加到30层,你的代码就需要大改,显然不合理,在写代码的过程中要避免写出这样的代码来。
然后讲真正意义上的第一种思路,即DFS(名词解释:DFS),在这里揭晓之前埋下的伏笔,“撞南墙”,实际上DFS就是一个不撞南墙不回头的过程,我们拿迷宫举例,DFS的算法就应该是:
1. 自己处在某一个路口,首先观察周围是否有可行的路(即不是墙且没有走过的路,可以想想为什么不能走“走过的路”),如果没有,直接返回失败,如果有路直接通向终点,直接返回成功,否则进入下一步;
2. 用一个集合记录下所有自己发现的与路口X相连的路。
3. 先走向第一条,进入一个新的路口,把新的路口称为,对重新执行第一步。
4. 如果执行到了这一句,只能说明没能走到终点,那么就先从返回。
5. 对乃至每一个进行第三步。
6. 如果执行到了这一步还是没能找到出口,说明迷宫不存在可行出口,返回失败。
当然,这个例子也仅仅是帮助大家理解DFS这个“不撞南墙不回头”的思路,即如果还有可行的路,我就一直走,直到撞墙了,再回头,找另一可行的条路。
当然,考虑到大家第一次写DFS,对于这一题的DFS解法我一会会贴出代码来供大家参考。
再讲第二种思路,比较巧妙,利用二进制数的特殊性质来求解,即状态压缩(名词解释:状态压缩)。我们知道每个灯只有亮灭两种情况,实际上就对应着二进制的“0”和“1”,那么我们有没有可能用一个长度不超过16的二进制数来表示所有情况呢?答案是肯定的。这样做的好处在于我们不需要嵌套地进行遍历了,只需要从0一路线性地遍历到,就可以遍历所有的情况。
然而这种思路的难点在于,我确实是遍历到了每一种情况,但我怎么把这个信息从二进制数里取出来呢?我们就要用到上一题里面学到的知识——位运算。对于每个遍历到的数,我们将其与进行按位与“&”操作,如果结果为0,说明的第个灯是灭的,不为0则说明是亮的。如此我们便可以将所有的信息都提取出来。对于按灯操作,我们也只需要研究这个操作具体影响了哪一位或哪几位,将其与进行按位异或“^”操作即可得到操作后的情况。对于检测最终的结果也非常的方便,我们只需要看其是否为0,如果不为0,说明没有全灭。总的来说,这样的做法比较巧妙,算是进阶解法,有兴趣的同学自己研究,我就不贴代码了。
#include <bits/stdc++.h> #define MAX 16 using namespace std; int m,n; int amap[MAX][MAX], copyMap[MAX][MAX]; //amap用于保存原灯阵和一次遍历后的灯阵, copyMap用于保存遍历后的灯阵并进行操作,两者要隔离防止互相干扰int ans = 256; //利用全局变量来更新最终答案,初始化为256是因为理论最大值为256 //定义一个操作,改变某处灯的情况void change(int amap[MAX][MAX], int row, int column){ if (amap[row][column] == 1) amap[row][column] = 0; else if (amap[row][column] == 0) amap[row][column] = 1; return; } //定义一个操作,模拟按下某个灯的情况void push(int amap[MAX][MAX], int row, int column){ change(amap, row, column); if (row - 1 >= 0) change(amap, row - 1, column); if (row + 1 < n) change(amap, row + 1, column); if (column - 1 >= 0) change(amap, row, column - 1); if (column + 1 < m) change(amap, row, column + 1); return; } //定义一个操作,用于复制整个灯阵,可以防止后续的操作破坏原有的结构void getCopy(){ for(int i = 0; i < n; i++){ for(int j =0; j < m; j++){ copyMap[i][j] = amap[i][j]; } } return; } //定义一个函数,用于获取总共的步骤int getStep(){ getCopy(); int step = 0; for(int i = 0; i < n - 1; i++){ for(int j = 0; j < m; j++){ if (copyMap[i][j] == 1){ push(copyMap, i + 1, j); step ++; } } } for(int j = 0; j < m; j++){ if(copyMap[n - 1][j] == 1) return -1; } return step; } //DFS主体void dfs(int index, int count){ if(index == m){ int lef = getStep(); if(lef == -1) return; else if(lef + count < ans) ans = lef + count; return; } push(amap, 0, index); dfs(index + 1, count + 1); push(amap, 0, index); //此处很重要,DFS在“回头”的时候一定要“恢复”原状态dfs(index + 1, count); } int main(){ scanf("%d %d",&n,&m); for(int i = 0; i < n; i++){ for(int j = 0; j < m; j++){ scanf("%1d",&amap[i][j]); } } dfs(0,0); printf("%d\n",ans); return 0; }
【BIT2021程设】2. 解谜游戏——初见DFS相关推荐
- Silverlight 解谜游戏 之十七 胜利界面优化
在<Silverlight 解谜游戏 之九 胜利通关>一文中我们制作了一个"You WIN" 的游戏结束界面,细心的同学可能发现当找到最后一个物品后GoToStateA ...
- Silverlight 解谜游戏 之七 放大镜(3)
在前两篇文章中,我们已经创建了"放大镜效果"和"放大镜CheckBox",本篇内容将通过CheckBox来控制放大镜效果的开/关状态,并完成全部"放大 ...
- 设计解谜游戏的30堂课
设计解谜游戏的30堂课 文章目录 1.什么是Eureka Moment? 2.谜题与幽默是同构的 3.最大限度提高Sparkle 4.避免无意义的谜题 5.惊喜是Sparkle的重要源泉 6.有趣的事 ...
- [2019蓝桥杯国赛B组c++][最优包含][排列数][解谜游戏][第八大奇迹]
个人题解链接,蓝桥杯历届试题,正在更新中~ 文章目录 个人题解链接,蓝桥杯历届试题,正在更新中~ 一个大佬写了填空题的答案,点击下面链接 最优包含 排列数 解谜游戏 第八大奇迹 一个大佬写了填空题的答 ...
- 未来科幻点击解谜游戏《英科迪亚》现已登陆NS
来源:中国数字科技馆 近未来科幻世界观的点击解谜游戏<英科迪亚>现已正式登陆 Switch 港服 eShop,支持中文. <英科迪亚>的背景设定在2062年极富赛博朋克气息的新 ...
- 解谜游戏-STEAM中最杂乱的游戏标签
--人活着,倾其一生,就是为了解开世界的谜. 自从游戏这个词出现开始,解谜元素可能就被人们灵活运用于各种娱乐中.在现在的游戏里更是普遍使用,或是帮助游戏营造出各种沉浸式氛围.或是直接增加游戏可玩性.或 ...
- bugku 杂项 就五层你能解开吗_你能解开这个和数字有关的逻辑解谜游戏吗? | 每日一考...
今天是一道和数字有关的逻辑解谜游戏看看你能用多长时间得到答案这道题的目标是,把网格根据数字划分成很多个方形小块. 每个数字都代表它所在的小块面积,也就是包含了几个小格子,要求如下图,每个小块里必须有, ...
- Silverlight 解谜游戏 之十六 消失的蒙娜丽莎
在<Silverlight 解谜游戏 之三 消除名单>中我们通过在物品轮廓画出Path 来达到消除物品的效果,由于游戏中的物品都是Office 图片的一部分所以无法使其真正消失,本篇我们将 ...
- 有趣的Python Challenge编程解谜游戏攻略二(5-9关)
**有趣的Python Challenge编程解谜游戏攻略二(5-9关)** 介绍 游戏介绍 0-4关攻略 写在前面 关卡 第5关 第6关 第7关 第8关 第9关 链接总结 第10关预告 介绍 游戏介 ...
- 冒险解谜游戏:恩科迪亚Encodya mac中文版
恩科迪亚Encodya for mac是Mac平台上一款科幻赛博朋克风格的冒险解谜游戏,在恩科迪亚mac版中你将会跟随孤儿Tina与守护她的机器人SAM-53一起展开一场冒险之旅,恩科迪亚游戏的背景设 ...
最新文章
- 欠拟合的原因以及解决办法(深度学习)
- 基于单片机的贪吃蛇游戏设计_前端入门,基于html,css,javascript的贪吃蛇游戏
- fastjson转list嵌套_FastJson的学习之JSON互相转Map集合,List集合,JavaBean
- 《Ansible权威指南》一1.7 Ansible的安装部署
- 未曾秋高气爽,亦然爬山去也
- bootstrap学习(一)栅格、布局
- nginx的日志文件配置
- Java数组排序解码
- NameNode之DataNode管理
- powerDesigner 把name项添加到注释(comment),完美方案!
- CO2 势能模型里面的 ε 的单位 理解
- css带三角形的对话框
- 模型参考自适应控制器(MRAC)系列: 2.提升瞬态性能
- 印刷质量缺陷的视觉检测原理概述
- 快速查看本机公网IP地址
- 一只菜鸟的前端实习记录(碎碎念)
- Toy例程导读(三).高级语言分析和转换
- 联想微型计算机功率,联想小新pro 13 2019 i7 突破功耗墙以后
- 服务器重启后,docker安装的mysql怎么重启
- AR502H-CN开发笔记54:OVF和OVA的区别
热门文章
- web前端期末大作业 基于HTML+CSS家乡主题毕业设计源码
- 识别到硬盘 计算机不显示盘符,Win10系统下移动硬盘可以识别但是不显示盘符的解决方法...
- 中国 省市区 code码
- 一文吃透 VS Code+Git 操作(vs code中git的相关配置与使用)
- Typora使用技巧之插入图片及图片上传
- 微信小程序的所有scene场景值 2020-10-21
- 2020期中考试总结
- python文件查重_使用Python查找目录中的重复文件
- excel利用vba批量生成word报告
- 不同Costa环鉴相器鉴别特性