有一个有按钮组成的矩阵 ,其中每行有6个按钮,共5行
每个按钮的位置上有一盏灯
当按下一个按钮后,该按钮以及周边位置(上边、下边、左边、右边)的灯都会改变
如果灯原来时点亮的,就会被熄灭 反之亦然
情况分析
***如果在矩阵的 角上 的按钮 改变 3 盏灯
如果在矩阵的边上 改变 4 盏灯
其余情况 这改变 5 盏灯***
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190404214605582.png)
与一盏灯毗邻的多个按钮被按下时,一个操作会抵消另一次操作的结果
给定矩阵中每盏灯的初始状态,求一种按按钮方案是的所有的灯都熄灭
输入
第一行时一个正整数,表示需要解决的按例数
每个案例有5行组成,每一行包括6个数字
这些数值以空格隔开,可以是0 或 1
0表示熄灭状态
1 表示点亮状态
输出:
每个案例先输出一行 :
输出字符串“PUZZLE#m” ,其中表示该案例的序号
接着按照该方案的输入格式 输出5行
1表示要把对应的按下
0表示不需要按下对应的按钮
样例输入 输入样例
2
0 1 1 0 1 0 PUZZLE #1
1 0 0 1 1 1 1 0 1 0 0 1
0 0 1 0 0 1 1 1 0 1 0 1
1 0 0 1 0 1 0 0 1 0 1 1
0 1 1 1 0 0 1 0 0 1 0 0
0 0 1 0 1 0 0 1 0 0 0 0
1 0 1 0 1 1 PUZZLE #2
0 0 1 0 1 1 1 0 0 1 1 1
1 0 1 1 0 0 1 1 0 0 0 0
0 1 0 1 0 0 0 0 0 1 0 0
1 1 0 1 0 1
1 0 1 1 0 1
解题分析
***第二次按下同一个按钮时,
可以抵消第二次按下时产生的结果
===>每个按钮最多只需要按下一次
各个按钮被按下的顺序对最终的结果没有影响
对第一行中每盏点亮的灯,按下的2 行对应的按钮有就可以熄灭第一行的全部灯
如此重复下去,可以熄灭第1,2,3,4行全部的灯***
**第一种想法:**
枚举所有可能的按钮状态,对每个状态计算一下最后灯的情况,看看是否都熄灭
每个按钮有两种状态(按下或不按下)
总共有30个开关,那么状态数是2^30,太多,会超时
因此需要减少枚举的状态数目
===>如果存在某个局部,一旦这个局部的状态被确定,那么剩余其他状态只能是确定的一种,或者不多的n种
那么就是只需要枚举这个局部状态即可
经发现, 第一行就是这样的 局部
因为 第1行的各个开关的状态确定的情况下,这些开关作用过后将导致第一行某些灯是亮的,某些灯是 灭的
要熄灭第一行 某个亮着的灯(假设唯一第i列),那么唯一的办法就是按下第二行第i列的开关(因为第1 行的开关已经用过,
而第3 行的开关不会影响到第1行)
为使第1行的灯全部熄灭 ,第2行的合理开关状态就是唯一的
同理,依次类推,直至最后一行,最后一行的状态也是唯一的
只需要将第一行的状态确定下来,记作A ,那么剩余行的情况就是确定唯一的
直到推出最后一行的开关状态,然后看看最后一行的开关起作用后 最后一行所有灯是否熄灭
如果是,A就是一个解的状态
若果不是,第一行换个状态重新在试试
只需要枚举第一行 的状态 , 状态数是2^6 = 64;
因为最近开始学习Java,就用Java方式实现:
public class LightOutProblem {
public static byte getBit(byte c, int i) {
return (c >> i) & 1;
}
public static byte setBit(byte c, int i, int v) { // 设置c的第i位为v
if (v == 1) //置1
c |= (1 << i);
//说明
// , i =2, c=00101 1 10
//00000001 <<2 ==> 00000 1 00
//c |= (1 << i) = 00101 1 10
//结果c=00101 1 10 第二位 置 1
else // 置0
c &= ~(1 << i);
// i =2, c=00101 1 10
//00000001 <<2 ==> 00000 1 00
// ~(1 << i) ==> 11111 0 11
// c &= ~(1 << i) ==> 00101 0 10 第二位 置 0
return c;
}
public static byte flip(byte c, int i) { // 将c的第i位为取反
c ^= (1 << i);
// 举个例子
//c = 00101110
//00000001 <<2 ==> 00000100
//结果c=00101010 第二位从2 置为 0
return c;
}
public static void outputResult(int t, byte[] result) {// 格式化输出结果
System.out.println("PUZZLE #" + t);
for (int i = 0; i < 5; ++i) {
for (int j = 0; j < 6; ++j) {
System.out.print(getBit(result[i], j));
if (j < 5)
System.out.print(" ");
}
System.out.println();
}
}
public static void main(String[] args) {
//因考虑到数据在不超过64 因此byte就足够 并 以 1位 表示一盏灯
byte[] oriLights; // 最初灯矩阵,一个比特表示一盏灯
byte[] lights; // 不停变化的灯矩阵
byte[] result; // 结果开关矩阵
byte switchs;// 某一行的开关状态
int T;
Scanner scan = new Scanner(System.in);
T = scan.nextInt();
for (int t = 1; t <= T; ++ t) {
oriLights = new byte[] { 0, 0, 0, 0, 0 }; //初始化所有数组
lights = new byte[] { 0, 0, 0, 0, 0 };
result = new byte[] { 0, 0, 0, 0, 0 };
for (int i = 0; i < 5; i++) { // 读入最初灯状态
for (int j = 0; j < 6; j++) {
byte s;
s = scan.nextByte();
oriLights[i] = setBit(oriLights[i], j, s);
}
}
for(int n = 0; n < 64; n ++) { //遍历首行开关的 64 种情况
lights = oriLights.clone();
switchs = (byte)n; //第i行开关状态
for(int i =0; i < 5; i++) {
result[i] = switchs;
for(int j = 0; j < 6; j ++) {
if(getBit(switchs,j) == 1) {
if(j > 0) {
lights[i] = flip(lights[i], j-1);//置反前一位
}
lights[i] = flip(lights[i], j);//置反当前位
if(j < 5) {
lights[i] = flip(lights[i], j+1);//置反后一位
}
}
}
if(i < 4) {
lights[i + 1] ^= switchs; //反转下一行
}
switchs= lights[i]; //将上一行的结过转到下一行
}
if (lights[4] == 0) {//如果最后一行是0 则说明所有的灯已关闭
outputResult(t, result);
break;
}
}
}
}
}
总结:
1、其中使用到了对于 学生 来说非常少用的 位运算 刚开始看的时候自己也没办法理解,只好每一步每一步写出来看,明白了此题也充分理解位运算;
2、在这个问题中,充分运用到枚举的算法,加上合适的分析,减少了枚举的规模。
3.在Java中,不像C语言那样,可以传入地址,也充分体现Java基本类型中,都是值传递的特性