一步一步地完成题目——费解的开关(C/C++语言)递推、递归、顺序思维
前言
本文中博主将一步一步地、以正常人的顺序思维完成题目——费解的开关,使用的核心方法是递推与递归。
题目

参考题目:费解的开关
详细的题目信息相信大家都已经知道了,因此这里为了简洁只展示输入输出格式及数据范围。
核心思维
本题利用递推做的核心思想很简单,即当这个5x5数组的第一行被处理完过后,想要开启第一行仍然灭着的灯,则必须点击该灯的下一行的相同位置。
因此,只要确定好第一行如何选择,其他行也自然确定了,之需要判断该种情况是否满足题目条件即可。
如图所示:

假设我们第一行只点一次,即被蓝色X的地方,点完后会变成这样:

如果我们想让第一行的第一个、第四个变亮,那么第二行的第一个、第四个就是必点的。
因此,我们只需要枚举第一行的所有选法,然后就能递推出整个四方体的选法,最后判断成是否成立。
写出数据输入格式
首先,先在主函数里写出题目要求的输入格式。先输入一个n,随后进行n次循环,每次循环都读入25个数据放在一个二维数组arr里。为了传参的时候方便,我们把二维数组放在外面,像这样:
int arr[5][5];
int main()
{
int n = 0;
scanf("%d", &n);
while (n--)
{
int i = 0;
for (i = 0; i < 5; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
scanf("%1d", &arr[i][j]);
}
}
//...
}
return 0;
}
注意: 本题在Acwing上数据输入时,每个数据之间没有空格,因此要控制scanf每次读取数据的宽度。代码中的“//...”代表接下来从此处开始写。
枚举第一行的选择
这里我们使用递归的方法,即在1 ~ 5里面选出1 ~ 5个数,每一种选法都是一种第一行的选择。创建递归函数dfs(int step),step代表当前枚举的位置,在外面创建数组choose代表递归时每个位置的状态,每次枚举当前位置选或者不选,五个位置都枚举结束后就代表形成了一种情况,随后利用判断函数jud对这种情况进行判断。
int main()
{
//...
dfs(0);//dfs的位置
}
return 0;
}
int arr[5][5];
int choose[5];
void dfs(int step)
{
if (step == 5)
{
jud(choose);
return;
}
//选
choose[step] = 1;
dfs(step + 1);
choose[step] = 0;
//不选
dfs(step + 1);
}
判断情况是否成立(1)
随后我们进行判断函数jud的书写,为了防止同一组数组不同的情况互相影响,我们创建一个临时的数组 _arr,复制arr的信息到其中,随后对 _ arr进行操作。
之后创建i和j,分别用于遍历行和列。
由于i和j的值不同,点灯还是灭灯的个数也不同(因为有可能在边界)。因此,我们创建一个函数change,用于改变arr【i】【j】周围能改变的灯的亮灭情况。
void jud(int* choose)
{
int _arr[5][5];
memcpy(_arr, arr, 25 * 4);
//对第一行进行操作
int i = 0;//用于遍历行
int j = 0;//用于遍历列
for (j = 0; j < 5; j++)
{
if (choose[j] == 1)//相当于arr【0】【j】被选择了
{
change(_arr, 0, j);
//...
}
}
}
实现亮灭改变函数
罗列情况,改变周围灯的亮灭情况,如果你不想写这么多的代码,也可以把刚开始创建的数组改为7x7大小,就可以不用考虑边界了。
void change(int _arr[5][5], int i, int j)
{
_arr[i][j] = !_arr[i][j];
if (i == 0)
{
_arr[i + 1][j] = !_arr[i + 1][j];
if (j == 0)
{
_arr[i][j+1] = !_arr[i][j+1];
}
else if (j == 4)
{
_arr[i][j-1] = !_arr[i][j-1];
}
else
{
_arr[i][j - 1] = !_arr[i][j - 1];
_arr[i][j + 1] = !_arr[i][j + 1];
}
}
else if (i == 4)
{
_arr[i - 1][j] = !_arr[i - 1][j];
if (j == 0)
{
_arr[i][j + 1] = !_arr[i][j + 1];
}
else if (j == 4)
{
_arr[i][j - 1] = !_arr[i][j - 1];
}
else
{
_arr[i][j - 1] = !_arr[i][j - 1];
_arr[i][j + 1] = !_arr[i][j + 1];
}
}
else
{
_arr[i - 1][j] = !_arr[i - 1][j];
_arr[i + 1][j] = !_arr[i + 1][j];
if (j == 0)
{
_arr[i][j+1] = !_arr[i][j+1];
}
else if (j == 4)
{
_arr[i][j - 1] = !_arr[i][j - 1];
}
else
{
_arr[i][j - 1] = !_arr[i][j - 1];
_arr[i][j + 1] = !_arr[i][j + 1];
}
}
}
判断情况是否成立(2)
因为对第一行的每一次选择也算走了一步,所以在每种情况下设置一个变量time,记录当前走了几步,一旦time超过6,就立马return。
注意: 第一行只有五个数,因此在第一行的选择中time不可能超过6,因此不需要在对第一行的选择中进行判断。
void jud(int* choose)
{
int _arr[5][5];
memcpy(_arr, arr, 25 * 4);
int time = 0;
//对第一行进行操作
int i = 0;//用于遍历行
int j = 0;//用于遍历列
for (j = 0; j < 5; j++)
{
if (choose[j] == 1)//相当于arr【0】【j】被选择了
{
time++;
change(_arr, 0, j);
}
}
//...
//对2,3,4,5行进行操作
}
随后对第2,3,4,5行进行选择,对第二行的选择次数,是源于第一行选择完之后还有几个灭着的灯。
因此,我们对上一行进行遍历,如果_arr【i-1】【j】==0,就把time+1,同时点一下_arr【i】【j】。
注意: 此时,time已经有可能超过6了,因此需要进行判断。
void jud(int* choose)
{
int _arr[5][5];
memcpy(_arr, arr, 25 * 4);
int time = 0;
//对第一行进行操作
int i = 0;//用于遍历行
int j = 0;//用于遍历列
for (j = 0; j < 5; j++)
{
if (choose[j] == 1)//相当于arr【0】【j】被选择了
{
time++;
change(_arr, 0, j);
}
}
//...
//对2,3,4,5行进行操作
for (i = 1; i < 5; i++)
{
for (j = 0; j < 5; j++)
{
if (_arr[i - 1][j] == 0)
{
time++;
if (time > 6)
{
return;
}
change(_arr, i, j);
}
}
}
//...
}
现在,我们已经对1 ~ 5行全部选择完毕,但是不确定是否全部都为1,因此需要进行遍历一次,一旦出现为0的情况,说明这种情况不可取,马上返回。
//检测数组中是否全部为1
for (i = 0; i < 5; i++)
{
for (j = 0; j < 5; j++)
{
if (_arr[i][j] == 0)
{
return;
}
}
}
//...
//运行到这里,说明此种方案可行
对输出数据的处理
题目要求我们输出所有可行的方案中步数最少的一种所消耗的步数,如果没有可行方案则返回-1。
因此,我们设置一个全局变量min_time并令其初始化为一个大于6的数,一旦出现一个time小于min_time,就把min_time更新为time。
如果还有更小的time,就能再次更新。
int arr[5][5];
int choose[5];
int min_time = 10;
//运行到这里,说明此种方案可行
min_time = time;
return;
//...
随后,我们进行最后的处理。
当dfs(0)结束之后,我们得到了一个min_time,因为它的初始值大于6,所以只要有可行方案存在,该值就一定会被改变,否则,它就依然还是原来的值。
所以,我们设置一个if语句,如果该值为10(初始值),代表没有可行方案,打印-1后换行。
如果该值不等于10,就打印这个数后换行,代表最小步数为该值。
注意: 因为min_time是我们在全局定义的,因此打印完了以后不要忘记再将其重新赋值为10哦。(博主改了很久才想到这一点,TAT)
int main()
{
int n = 0;
scanf("%d", &n);
while (n--)
{
int i = 0;
for (i = 0; i < 5; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
scanf("%1d", &arr[i][j]);
}
}
dfs(0);
if (min_time == 10)
{
printf("-1\n");
}
else
{
printf("%d\n", min_time);
min_time = 10;
}
}
return 0;
}
感谢您的阅读与耐心~ 如有错误烦请指出~
一步一步地完成题目——费解的开关(C/C++语言)递推、递归、顺序思维的更多相关文章
- ACM_递推题目系列之一涂色问题(递推dp)
递推题目系列之一涂色问题 Time Limit: 2000/1000ms (Java/Others) Problem Description: 有排成一行的n个方格,用红(Red).粉(Pink).绿 ...
- ACM_递推题目系列之二认错人(递推dp)
递推题目系列之二认错人 Time Limit: 2000/1000ms (Java/Others) Problem Description: 国庆期间,省城HZ刚刚举行了一场盛大的集体婚礼,为了使婚礼 ...
- 一步一步写算法(之挑选最大的n个数)
原文:一步一步写算法(之挑选最大的n个数) [ 声明:版权所有,欢迎转载,请勿用于商业用途. 联系信箱:feixiaoxing @163.com] 从一堆数据中挑选n个最大的数,这个问题是网上流传的 ...
- 一步一步写算法(之n!中末尾零的个数统计)
原文:一步一步写算法(之n!中末尾零的个数统计) [ 声明:版权所有,欢迎转载,请勿用于商业用途. 联系信箱:feixiaoxing @163.com] 在很多面试的题目中,求n!结果中零的个数也是 ...
- 一步一步理解线段树——转载自JustDoIT
一步一步理解线段树 目录 一.概述 二.从一个例子理解线段树 创建线段树 线段树区间查询 单节点更新 区间更新 三.线段树实战 -------------------------- 一 概述 线段 ...
- 如何一步一步用DDD设计一个电商网站(九)—— 小心陷入值对象持久化的坑
阅读目录 前言 场景1的思考 场景2的思考 避坑方式 实践 结语 一.前言 在上一篇中(如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成),有一行注释的代码: public interfa ...
- 如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成
阅读目录 前言 建模 实现 结语 一.前言 前面几篇已经实现了一个基本的购买+售价计算的过程,这次再让售价丰满一些,增加一个会员价的概念.会员价在现在的主流电商中,是一个不大常见的模式,其带来的问题是 ...
- 如何一步一步用DDD设计一个电商网站(十)—— 一个完整的购物车
阅读目录 前言 回顾 梳理 实现 结语 一.前言 之前的文章中已经涉及到了购买商品加入购物车,购物车内购物项的金额计算等功能.本篇准备把剩下的购物车的基本概念一次处理完. 二.回顾 在动手之前我对之 ...
- 如何一步一步用DDD设计一个电商网站(七)—— 实现售价上下文
阅读目录 前言 明确业务细节 建模 实现 结语 一.前言 上一篇我们已经确立的购买上下文和销售上下文的交互方式,传送门在此:http://www.cnblogs.com/Zachary-Fan/p/D ...
- 如何一步一步用DDD设计一个电商网站(六)—— 给购物车加点料,集成售价上下文
阅读目录 前言 如何在一个项目中实现多个上下文的业务 售价上下文与购买上下文的集成 结语 一.前言 前几篇已经实现了一个最简单的购买过程,这次开始往这个过程中增加一些东西.比如促销.会员价等,在我们的 ...
随机推荐
- Web3区块链DAS域名注册教程 tron trx链波卡钱包地址解析 用户名转账 ENS
而在去中心化系统中,大部分人充值.转账时,使用的都是区块链原生的长地址,比如: ETH 的地址: 0x9euo8sHip*******dHld90 CKB 的地址: ckHUEI829D******* ...
- .Net 7 被Microsoft的开源免费PowerToys工具独立附带
楔子 什么是PowerToys? Microsoft PowerToys 是一组实用工具,可帮助高级用户调整和简化其 Windows 体验,从而提高工作效率. 简而言之,就是给最新的windows11 ...
- emqtt-bench
安装 安装环境:Centos7 安装包:emqtt-bench-0.4.6-alpha.2-centos7-amd64.tar.gz 建议使用已编译好的发行包进行安装,源码编译已踩坑. # 创建存放目 ...
- 【Dubbo3 终极特性】「云原生三中心架构」带你探索 Dubbo3 体系下的配置中心和元数据中心、注册中心的原理及开发实战(中)
承接上文 通过之前的[Dubbo3终极特性]「云原生三中心架构」带你探索 Dubbo3 体系下的配置中心和元数据中心.注册中心的原理及开发实战(上),让我们对Dubbo3的三中心架构体系有了一定的认识 ...
- 浏览器刷新时候不删除信息,关闭后删除用户信息处理办法,浏览器监听刷新以及删除事件、cookie、session、sessionStorage、localStorage区别
首先我们可以了解到:sessionStorage 不在不同的浏览器窗口中共享,即使是同一个页面: localStorage 和 cookie 在所有同源窗口是共享的 那么我们可以根据用户不同需求来进行 ...
- 转载:SQL分页查询总结
[转载]SQL分页查询总结 开发过程中经常遇到分页的需求,今天在此总结一下吧. 简单说来方法有两种,一种在源上控制,一种在端上控制.源上控制把分页逻辑放在SQL层:端上控制一次性获取所有数据,把 ...
- C语言定制DEBUG信息
C语言定制DEBUG信息 背景与问题 在日常编写程序中,我们经常需要输出一些调试信息帮助我们 DEBUG 或者更好的编程,通常我们的做法是这样的: 在需要的地方直接printf(...)输出有用的信息 ...
- 一文搞懂工作流审批(Java+activiti)快速开发+自定义工作流配置
前言 activiti工作流引擎项目,企业erp.oa.hr.crm等企事业办公系统轻松落地,一套完整并且实际运用在多套项目中的案例,满足日常业务流程审批需求. 一.项目形式 springboot+v ...
- C#Autofac依赖注入批量注入 (目前版本.netcore3.0)
上一文:C#依赖注入一看就会系列 链接:C#依赖注入(直白明了)讲解 一看就会系列 - 22222220 - 博客园 (cnblogs.com) 上一次我们知道了 为什么要依赖注入,这次我们就用使用人 ...
- JAVA虚拟机14 类加载器
1.简介 Java虚拟机设计团队有意把类加载阶段中的"通过一个类的全限定名来获取描述该类的二进制字节流"这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需的 ...