AC95. 费解的开关
题目描述
25盏灯排成一个5x5的方形。每一个灯都有一个开关,游戏者可以改变它的状态。每一步,游戏者可以改变某一个灯的状态。游戏者改变一个灯的状态会产生连锁反应:和这个灯上下左右相邻的灯也要相应地改变其状态。
我们用数字“1”表示一盏开着的灯,用数字“0”表示关着的灯。
思考了一天,都晕了,明白大概的想法是:每一行的暗灯都由下面一行去点亮。这个倒是明白。就是有一个点没明白:为什么要枚举第一行所有按法?
我觉得吧,枚举第一行的所有按法是用来减少步数的,我之前一直觉得从第二行直接看就好,但是从第二行开始其实就已经固定了最后的答案,这样的解不一定是最少的甚至可能超出范围而没有解。
所以,枚举第一行的意义是:不需要在意第一行的灯是灭是暗,只需把第一行的按法枚举一遍,也就是我们说的 “操作”,每个位置都有两种选择,按(用1表示)或者不按(用0表示),遍历这32种操作引发的情况,每一次再通过res = min(res, step);
把最小步数存一下,就能找到最优解
Note:
- 枚举第一行时:1表示按一下,0表示不按(当然反过来也可以啦~看你)
- 在遍历整个矩阵时:1是灯亮,0是灯灭
- memcpy 可以用来复制数组,这里是先把原数组备份一下,然后对本数组操作,本次操作结束后,要再把备份数组还原回来,再进行下一次操作啦~
下面是代码~
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 6;
int dx[N] = {-1, 0, 1, 0, 0}, dy[N] = {0, 1, 0, -1, 0};
char g[N][N], backup[N][N];
// 这个操作是把(x, y)以及上下左右的灯都变成相反的颜色
void turn (int x, int y)
{
for (int i = 0; i < 5; i ++ )
{
int a = x + dx[i], b = y + dy[i];
//如果在边界外边,直接忽略即可
if (a < 0 || a >= 5 || b < 0 || b >= 5) continue;
g[a][b] ^= 1; //异或,不同的时候就变成相反的数
}
}
int main()
{
int n;
scanf("%d", &n);
while(n -- )
{
// 按行输入,把每一行当成一个字符串
for (int i = 0; i < 5; i ++ ) cin >> g[i];
int res = 10;
// 这里我们枚举了第一行的32种按法,不用管是亮是灭,把第一行所有情况都按一遍
// 按每种情况的第一行,去遍历接下来的行
// 枚举32种第一行的按法只是可能会减少步数,如果直接从第二行开始答案一定是固定的了,找不到最优解或者可能没有解
for (int op = 0; op < 32; op ++ )
{
// 我在对这种情况操作的时候,得先备用一下
// 把原始数组备份一下,然后操作g,操作完了还原,然后再操作
memcpy(backup, g, sizeof g);
int step = 0;
// 第一行的按法(在这里 1 表示按了, 0 表示不按),这里只是为了输出第一行按完之后的状态
for (int i = 0; i < 5; i ++ )
if (op >> i & 1) // 数字2 对应了 00010 表示第2个位置的按一下
// 00010 >> 1 & 1 是1 所以turn(0, 1) 就是第一行第二个位置
{ // 数字3 对应了00011 表示第1 和第2个位置的按一下
step ++ ;
turn (0, i);;
}
// 然后通过第一行按完之后的状态,按234行
for (int i =0; i < 4; i ++ )
for (int j = 0; j < 5;j ++ )
if (g[i][j] == '0')
{
step ++;
turn (i + 1, j); // 如果这个位置是灭的,就按下一行对应的位置
}
bool dark = false;
for (int j = 0; j < 5; j ++ )
if (g[4][j] == '0')
{
dark = true;
break;
}
// 对于32种情况的这一种,如果所有的全亮就记录下步数(事实上只记录了最后一行是否dark)
if (!dark) res = min(res, step);
memcpy (g, backup, sizeof g);
}
if(res > 6) res = -1;
cout << res << endl;
}
return 0;
}
枚举第一行的意义是:不需要在意第一行的灯是灭是暗,只需把第一行的按法枚举一遍,也就是我们说的 “操作”,每个位置都有两种选择,按(用1表示)或者不按(用0表示),遍历这32种操作引发的情况,每一次再通过res = min(res, step);把最小步数存一下,就能找到最优解
这个解释是真的精辟,厉害!
第一行枚举的并不是灯亮不亮,而是对该行所有灯按不按的所有情况的枚举,第一行的按法一旦确定(第一行的灯亮不亮也就确定了),后面的状态就全部确定了
看到这,茅塞顿开
感谢!希望24年4月蓝桥杯省赛,广东C++A组省一!
4.13省赛 希望上海C++A组省一!
希望25年4月 蓝桥杯省赛 湖南c++B组省一
想了好久 我也还是没懂为什么要枚举第一排32种方案 第一排灯亮暗是输入的是已知的,那就应该针对第一排的灯接着往下递归啊,但这么做答案就是固定的,也不确定是最小步,可是枚举所有方案的意义在哪啊 没想明白啊 求大佬解答
啊 又往下翻了翻评论 我好像是悟了。。就是说我们输入的已知的是第一行灯亮或暗的状态,而我们枚举的32种是我们对灯的操作,按还是不按。如果通过操作使得第一行灯的亮暗状态发生了改变,那么接下来我们对第二行的操作就也会随之改变,继而导致整个步数都会有变化,所以用res来留存最小的。
思路:先对第一行进行32种操作的枚举,列出所有操作后第一行可能的状态(操作算步数 step++),一旦第一行的每盏灯的亮暗情况确定了,那么该方案的步数也就确定了
太对了,感谢,真的是费解哈哈
终于懂了!!!感谢感谢
无语了,睡了一觉又不会了。。
哈哈哈
昨天我又想了想,回了!这应该可以记上两天。其实有个误区就是,我之前以为第一行亮着的不能按,其实可以的
其实感觉就是:
1. 第一行本身的亮灭状态并不能直接决定我们按的方案(最小)
2. 由于第一行已经操作过后,为了得到全亮,只能通过下一行改变(因此确定了后续每一行的操作)
难点在于如何理解点1,对于第一行,我们要意识到:可以通过按一些亮的(大家第一直觉都想着的是操作灰灯)达到总操作数最小的目的
悟了
大写的牛牛
太感谢了, 终于明白了, 谢谢大佬!!!!
nb!!
感谢感谢 想了一天了,终于懂了
看到这里终于明白了,原来枚举的是对灯的操作,谢谢大佬!!!
这样能枚举出所有的需要的步数吗?
看乐了
懂了,先不管用了几步,反正就是把第一行5个位置共32种按法都先按一遍在从第二行开始找每种方法的res数,最后取出最小的那个!
判断最后一行 bool dark的时候 ,那个函数该怎么理解啊
为什么要判断g[4][j] 这个点是否为0呀,是0的话不就是关着的嘛?这边不太理解
因为最后一行不可以再操作了,所以如果有灯是黑的说明这个方案就是不可行的
就是您可以给我讲一下bool函数嘛,我不太理解那个bool函数是什么意思
什么布尔函数,最后的变量dark嘛?我理解就是首先假设dark为假(也就是最后一排灯全亮了,此时说明这个方案是可行的),然后for一下最后一行,如果有暗的(也就是为‘0‘),那么此时dark为真表示最后一排有灯是暗的,那就不用再找了说明这个方案不可行,因为最后一行不能再操作了,再操作的话就会改变上一行的值
我们枚举的i>>j&1时的0或者1,只是表示该位置上按或者不按,而与初始时第一行的状态无关,并不表示亮或者不亮。
枚举时也是按照第一行按或者不按一共32种方法。
关于为什么枚举第一行,我认为是因为接下来的2~n 行都会对应上一行的状态用turn函数进行改变,那么唯一没用过turn函数的就是第一行,这种情况下不能保证所得结果是最小值,那么枚举第一行的所用可能的摁法,然后在每种摁法下再对2~n行进行操作,最后取所有枚举的最小值,这也就是min(res, step)的意义!!!
大佬,if (op >> i & 1) 这是啥意思啊(迷糊)
是为了找到当前这个op情况下哪些数字是1.是1的话就需要按
为啥不是0来按,0是暗的
就是说我们输入的已知的是第一行灯亮或暗的状态,而我们枚举的32种是我们对灯的操作,按还是不按。如果通过操作使得第一行灯的亮暗状态发生了改变,那么接下来我们对第二行的操作就也会随之改变,继而导致整个步数都会有变化,所以用res来留存最小的。
思路:先对第一行进行32种操作的枚举,列出所有操作后第一行可能的状态(操作算步数 step++),一旦第一行的每盏灯的亮暗情况确定了,那么该方案的步数也就确定了(借用一下前面YeaH的话)
不管是按1还是按0,所有的情况都会被枚举一次。
为什么要把g数组存到backup里呢
同问
因为我们需要数组来检验每种方案的可行性,如果我们把原数组改变了,即原数组变成这种方案的最终结果,但是我们还想在枚举其它方案,回不去了
对第一行灯泡用位运算进行按还是不按的处理那边,应该是turn(0,4-i)而不是turn(0,i)
虽然他不影响正确答案(因为所有可能都枚举到了)
枚举第一行是暴力的思想,后面才是递推。枚举第一行就是解空间,在这个空间找最小。这个题目就是暴力+递归
递推不是递归
感觉就是不管他给你的初始第一行状态是怎样,你都可以通过其他的操作来改变他,进而求出最小值
枚举第一行可以理解为
我们只是枚举第一行每个位置按还是不按,与亮灭没有关系
枚举完第一行的情况后 后面都确定了,只需要再看最后一行作为终止条件
枚举第一行按不按与题目给的原第一行不冲突,还是要再判断一次原第一行的
看了两小时以为先枚举的是第一行按了之后的情况,怎么看怎么怪,原来枚举的是第一行的所有按法不是情况,这下终于懂了😂
我是菜鸟
有没有可能利用贪心思想,统计一下四个方位的全部0的个数,用大根堆来存储,我每次按那个能够将最多0的转化为1的点,我能够得到最优解?
你好!我想问一下为什么要用res和step这两个?用一个来保存步数不就可以了吗?
想问一下,为啥这里可以用char ^ 1,但我直接用 ‘1’ ^ 1就会报错
我的理解是:只要出现在5*5的表格中的开关都可以进行操作,所以第一行也不例外;然而遍历第一行的32种情况会影响下面几行的开关操作数,进而影响到最后要求的最少操作数,所以一定要遍历第一行的情况。
$\texttt{tplqp}$