算法入门(一) 枚举
关灯问题 (POJ1222)
1.题目描述:
2.解题分析:
可以看出当针对第一行中的开关灯的位置确定后,第一行的灯应该是处于有些灯是被熄灭,有些灯还是开启的。因为当第一行灯的开关按钮已经确定(已经确定哪些位置要按下按钮),这些第一行还亮着的灯只有通过按下第二行的灯来熄灭了!比如按完第一行的按钮后,第一行中第1,2,5列的灯还是开着的,那么要使这些灯熄灭,只有按下第二行的第1,2,5列才有可能熄灭第一行的所有灯。通过这种思路,当确定了第一行每个位置是否按下按钮后,要使得所有的灯熄灭,第二行要按下按钮的列位置已经确定,就是第一行中那些还开着灯的列。同理,当第二行的灯按下后,依然还有亮着的灯,要使这些灯熄灭,必须按下第三行对应的列位置的按钮。第四行,第五行同理。那么怎么判断我们的熄灯方案是否使得全部的灯熄灭呢?上面提到的熄灯策略一定会保证前面四行的灯全部熄灭,所以只要检查最后一行的灯是否是熄灭的就行,如果最后一行的灯也是熄灭的,那么所有灯都是熄灭的!
所以这题只需要枚举第一行(对列枚举也可)灯的熄灭策略(对应位置是否按下按钮0 0 0 0 0 0 - 1 1 1 1 1 1,1表示按下按钮)即可,因为当第一行的策略决定后,其后要是灯全部熄灭的方案同时也确定了。
3.代码:
#include<iostream>
#include<string.h>
#include<string>
using namespace std;
int light[7][8];
int tmp[7][8]={0};
void press(int i, int 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;
else
return false;
}
int main()
{
int cs; //数据组数
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}; //button用来记录按法
int count=0;
while(count<64) //枚举第一排的64种按法,从000000到111111
{
memcpy(tmp,light,sizeof(light)); //拷贝函数
for(int i=1;i<=6;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++)
for(int j=1;j<=7;j++)
{
if(i!=1)
button[i][j]=0; //除第一行外清0
}
button[1][1]++; //二进制数加一
count++;
}
}
}
return 0;
}