POJ1222 熄灯问题

1、问题分析
题目给出一个5x6的01矩阵,0表示该位置的灯是熄灭的,1表示该处的灯是开着的,题目要求给出一种熄灯方案使得最后按下按钮后所有的灯都被熄灭。这里按下某处的按钮并不是只有该处的灯被熄灭或开启,而是如果它周围的灯(如果有的话)的状态都会反转(原来开着的灯熄灭,熄灭的灯开启)。具体的描述见原题。题目地址

2、求解思路
本题可以采用枚举法求解,具体为什么用枚举法可以看以下的分析。

首先注意到原文题目中Note中的第三点。原文如下:
3. As illustrated in the second diagram, all the lights in the first row may be turned off, by pressing the corresponding buttons in the second row. By repeating this process in each row, all the lights in the first four rows may be turned out. Similarly, by pressing buttons in columns 2, 3 ?, all lights in the first 5 columns may be turned off.

可以看出当针对第一行中的开关灯的位置确定后,第一行的灯应该是处于有些灯是被熄灭,有些灯还是开启的。因为当第一行灯的开关按钮已经确定(已经确定哪些位置要按下按钮),这些第一行还亮着的灯只有通过按下第二行的灯来熄灭了!比如按完第一行的按钮后,第一行中第1,2,5列的灯还是开着的,那么要使这些灯熄灭,只有按下第二行的第1,2,5列才有可能熄灭第一行的所有灯。通过这种思路,当确定了第一行每个位置是否按下按钮后,要使得所有的灯熄灭,第二行要按下按钮的列位置已经确定,就是第一行中那些还开着灯的列。同理,当第二行的灯按下后,依然还有亮着的灯,要使这些灯熄灭,必须按下第三行对应的列位置的按钮。第四行,第五行同理。那么怎么判断我们的熄灯方案是否使得全部的灯熄灭呢?上面提到的熄灯策略一定会保证前面四行的灯全部熄灭,所以只要检查最后一行的灯是否是熄灭的就行,如果最后一行的灯也是熄灭的,那么所有灯都是熄灭的!

所以这题只需要枚举第一行(对列枚举也可)灯的熄灭策略(对应位置是否按下按钮0 0 0 0 0 0 - 1 1 1 1 1 1,1表示按下按钮)即可,因为当第一行的策略决定后,其后要是灯全部熄灭的方案同时也确定了。

具体编程时,按下按钮时,如果没有到达边界,它四周位置都有灯,那么就把四周位置的灯的状态都发生改变,如果到达边界除了则需要另外操作。这个问题可以通过设置更大的数组来简化,原题的灯是5x6的矩阵,可以在其四周填0构成7x8的矩阵来简化操作。填充最外一层的0只是为了避免边界判断。

3、参考代码

#include <iostream>
#include<string.h>
using namespace std;

int light[7][8] = {0};
int tmp[7][8] = {0};//light数组的复制

void press(int i, int j)//按下第i行,第j列后
{
	tmp[i][j] = !tmp[i][j];
	tmp[i][j-1] = !tmp[i][j-1];
	tmp[i][j+1] = !tmp[i][j+1];
	tmp[i-1][j] = !tmp[i-1][j];
	tmp[i+1][j] = !tmp[i+1][j];
}

bool check()//检查灯是否全部熄灭
{
	int check = 0;//熄灭灯的数量
	for(int i = 1; i<= 6; i++)
	{
		if(tmp[5][i] == 0)
		{
			check++;
		}
	}

	if(check==6)
		return true;

	return false;
}

int main()
{
	int cs;//case情况数
	cin>>cs;

	int puzzle = 0;
	while(cs--)
	{
		puzzle++;
		for (int i = 1; i <=5 ; ++i)//输入数据
		{
			for(int j = 1; j<=6; j++)
			{
				cin>>light[i][j];
			}
		}

		int button[7][8] = {0};

		int count = 0;
		while(count < 64)//枚举第一行按钮的64种按法,从000000-111111
		{
			memcpy(tmp, light, sizeof(light));//void *memcpy(void *dest, const void *src, size_t n);  头文件string.h
			for(int i = 6; i >= 1; i-- )
			{
				if(button[1][i] == 2)
				{
					button[1][i-1]++;
					button[1][i] = 0;
				}
			}

			for(int i = 1; i<= 6; i++)//摁下第一行的按钮
				if(button[1][i] == 1)
					press(1, i);

			for(int i = 2; i <= 5; i++)
			{
				for(int j = 1; j <= 6; j++)
				{
					if(tmp[i-1][j]==1)
					{
						press(i, j);
						button[i][j] = 1;
					}
				}
			}

			if(check())
			{
				cout<<"PUZZLE #"<<puzzle<<endl;
				for(int i = 1; i <= 5; i++)
				{
					for(int j = 1; j <= 6; j++)
					{
						cout<<button[i][j]<<" ";
					}
					cout<<endl;
				}
				break;//枚举成功则退出枚举
			}
			else//为下一次枚举做准备
			{
				for(int i=0;i<7;i++)//对一种解法枚举后重新置button数组除第一行外的值为0
					for(int j=0;j<8;j++)
					{
						if(i!=1)
							button[i][j] = 0;
					}


				button[1][6]++;//枚举二进制
				count++;
			}

		}
	}

	return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值