枚举算法:熄灯问题

问题描述:

1)有一个由按钮组成的矩阵,其中每行有6个按钮,共5行。

2)每个按钮的位置上有一盏灯。

3)当按下一个按钮后,该按钮以及周围位置(上边、下边、左边、右边)的灯都会改变一次。

即,如果灯原来是点亮的,就会被熄灭;如果灯原来是熄灭的,则会被点亮。

在矩阵角上的按钮改变3盏灯的状态;在矩阵边上的按钮改变4盏灯的状态;其他的按钮改变5盏灯的状态。如图所示:

与一盏灯毗邻的多个按钮被按下时,一个操作会抵消另一次操作的结果。

问:给定矩阵中每盏灯的初始状态,求一种按按钮的方案,使得所有的灯都熄灭。

输入:

1)第一行是正整数N,表示需要解决的案例数

2)每个案例由5行组成,每一行包括6个数字

3)这些数字以空格隔开,可以是0或1

4)0表示灯的初始状态是熄灭的,1表示灯的初始状态是点亮的

输出:

1)对每个案例,首先输出一行,输出字符串“PUZZLE #m”,其中m是该案例的序号

2)接着按照该案例的输出格式输出5行,表示需要把对应的按钮按下,0表示不需要按对应的按钮,每个数字以空格隔开

样例输入:

2

0 1 1 0 1 0 

1 0 0 1 1 1 

0 0 1 0 0 1 

1 0 0 1 0 1 

0 1 1 1 0 0 

0 0 1 0 1 0 

1 0 1 0 1 1 

0 0 1 0 1 1 

1 0 1 1 0 0 

0 1 0 1 0 0 

样例输出:

PUZZLE#1

1 0 1 0 0 1

1 1 0 1 0 1

0 0 1 0 1 1

1 0 0 1 0 0 

0 1 0 0 0 0

PUZZLE#2

1 0 0 1 1 1

1 1 0 0 0 0

0 0 0 1 0 0

1 1 0 1 0 1

1 0 1 1 0 1

 

问题的分析:

主要谈一下自己对这道题难点的几个方面的认知:

1)问题的处理应该以何种思路进行,即算法的套路问题

2)如何存储输入输出的参数,用二维数组还是其他的方式

3)如何处理按灯过程中,多个灯发生变化的操作,即在程序中的实现问题

原先在思考这个问题的过程中,我是把第二个问题放在第一位的,因为从撸码的难以性的角度,可能会无脑的直接用数组定义一个bool类型,去存每一个输入的0和1参数,如果以这样的方式进行下面的代码编写,则在编码上,会出现一些问题。这个问题目前先放着,我们从第一个问题开始思考。

问题的思考及解决:

1)第一个问题即为如何处理这样的一个问题。首先不要从如何撸码的角度分析,而是想,如果自己碰到这样的熄灯问题,如何人为解决这样的问题。随机的按一通,你会发现完全没办法去解决这样的问题。枚举是这题的思路启发点。即把所有的可能性都列出来,然后挨个个去尝试。题目给出灯的开关一共有30个,那么要枚举2^30次。当然这个方式操作下来有点坑,等所有的情况都枚举完了,考试也该结束了。所以时间上肯定是不可行的。

所以必须要想办法减少枚举的次数。假设我们就枚举一行,从第一行来看,一共6个开关,2^6=64个状态,第一行开关确定的情况下,那么第二行开关是有唯一的按法,可以将第一行的灯给关掉的。以此类推,一直到最后一行,如果运气好,最后一行的灯正好都熄了,那么问题就解决了,如果没有解决,那么继续尝试下一个状态的按开关的方式。

以上是解决这个问题的核心思路。接下来再看看其他的一些细节问题。

2)关于数据的存储方式,我上面提到了,最常用的数组,因为状态只有0和1,所以用bool型,相比较int型,可以减少存储的空间。在这种存储方式的情况下,我们准备开始进行枚举了,一共有64种状态,6个数,是个排列组合的问题。在脱离计算机,手写排列的方式比较的轻松,但是在计算机上如何进行枚举,变得头疼了。如果说,这64个状态,存储在一个6位的二级制数中,通过累加的方式进行枚举开关的状态,这样枚举不是很方便。以此引出了位运算的方式解决这题的方法。如果要通过位运算,那么定义的二维数组显得有些冗余。直接定义一维数组,用char类型,既占空间小,又方便进行枚举。

3)这个问题主要是接着问题2的,由于进行位运算,如何处理好开关上下左右的状态是代码中的一个重点。这块细节见代码,并且在这块多说一句,在处理开关下端的状态用了方式和处理左中右的方式有所不同,显得比较的巧妙。

处理灯的左、中、右的方式和处理下一行灯的开关状态的方式不同,左中右均进行单一的位运算,而下一行的,用一条语句直接完成。可谓非常巧妙。

最后总结一下这个算法对于自己个人的提升:

1)对于位运算的取值,置数,取反的写法有所了解。(这题核心点之一就在于位运算的写法上)工作中从来没接触过位运算。

2)对于枚举算法的思路提升。

 

代码部分:

#include <memory>
#include <string>
#include <cstring>
#include <iostream>
using namespace std;

int GetBit(char c, int i)//取c的i位的数字
{
	return (c >> i) & 1;
}

void SetBit(char& c, int i, int v)//将c的i位设置为v
{
	if (v)
		c = (v << i) | c;
	else
		c = ~(v << i) & c;
}

void Flip(char &c, int i)//翻转c的i位取反
{
	c ^= (1 << i);//异或操作,相同的为0,不同的为1
}

void OutPutResult(int t, char result[])//进行结果输出
{
	cout << "PUZZLE #" << t << endl;
	for (int i = 0; i < 5; i++)
	{
		for (int j = 0; j < 6; j++)
		{
			cout << GetBit(result[i], j);
			if (j < 5)
				cout << " ";
		}
		cout << endl;
	}
}

void main()
{
	char oriLights[5];//初始亮灯情况
	char lights[5];//当前灯的状态
	char result[5];//按开关的结果
	char switch_s;//开关状态
	int num;
	cin >> num;
	for (int t = 1; t <= num; t++)
	{
		memset(oriLights, 0, sizeof(oriLights));//对数组赋值,全部为0
		for (int i = 0; i < 5; i++)//将灯的当前状态录入到数组oriLights中
		{
			for (int j = 0; j < 6; j++)
			{
				int s;
				cin >> s;
				SetBit(oriLights[i], j, s);
			}
		}
		for (int n = 0; n < 64; n++)
		{
			memcpy(lights, oriLights, sizeof(oriLights));//将oriLights中的参数复制到lights中
			switch_s = n;//开关的状态
			for (int i = 0; i < 5; i++)
			{
				result[i] = switch_s;
				for (int j = 0; j < 6; j++)
				{
					if (GetBit(switch_s, j))
					{
						if (j > 0)
							Flip(lights[i], j - 1);//改左灯状态
						Flip(lights[i], j);//改当前位置状态
						if (j < 5)
							Flip(lights[i], j + 1);//改右灯状态
					}
				}
				if (i < 4)
					lights[i + 1] ^= switch_s;//改下一行灯的状态,这波操作比较骚~
				switch_s = lights[i];//第i+1行按灯的方式与第i行灯的情况相同
			}
			if (lights[4] == 0)
			{
				OutPutResult(t, result);
				break;
			}
		}
	}
}

 

 

 

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值