熄灯/开灯算法
2025-08-27 13:55:34

例题

熄灯问题

有一个由按钮组成的矩阵,其中每行有6个按钮,共5行。每个按钮的位置上有一盏灯。当按下一个按钮后,该按钮以及周围位置(上边、下边、左边、右边)的灯都会改变一次。即,如果灯原来是点亮的,就会被熄灭;如果灯原来是熄灭的,则会被点亮。在矩阵角上的按钮改变3盏灯的状态;在矩阵边上的按钮改变4盏灯的状态;其他的按钮改变5盏灯的状态。

Input:

5行组成,每一行包括6个数字(0或1)。相邻两个数字之间用单个空格隔开。0表示灯的初始状态是熄灭的,1表示灯的初始状态是点亮的

Output:

5行组成,每一行包括6个数字(0或1)。相邻两个数字之间用单个空格隔开。其中的1表示需要把对应的按钮按下,0则表示不需要按对应的按钮。

解题思路

注意到此类问题的三个性质:

  • 每个位置至多被点击一次
  • 点击顺序不影响最终结果
  • 固定第一行后,满足题意的方案至多有一种

性质三的分析:当第i行某一位为1时,若前i行已经固定,只能点击第i+1行同一列的位置使得1变为0,从上到下对行使用归纳法即可得到性质。

采用枚举的方法,先枚举第一行的所有点击方式,之后不断使用性质三,到最后一行时判断是否全为0,然后可进行回溯

Tip1

第一行的点击方式可用位运算枚举,枚举0~63,若(k>>(j-1))&1, 点击(1,j)

Tip2

可以适当地开大数组,避免边角越界or特判

AC Code

C++ AC Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include<iostream>
using namespace std;
int map[7][8];
int cp[7][8];
int ans[7][8];
inline void reset(){
for(int i=1;i<=5;i++){
for(int j=1;j<=6;j++){
map[i][j]=cp[i][j];
ans[i][j]=0;
}
}
}
inline void set(int i,int j){
int x[]={i-1,i,i,i,i+1};
int y[]={j,j,j+1,j-1,j};
for(int k=0;k<=4;k++){
map[x[k]][y[k]]=1-map[x[k]][y[k]];
}
ans[i][j]=1;
}
int main(){
for(int i=1;i<=5;i++){
for(int j=1;j<=6;j++){
cin>>map[i][j];
cp[i][j]=map[i][j];
}
}
for(int k=0;k<=63;k++){
for(int j=1;j<=6;j++){
if((k>>(j-1))&1){
set(1,j);
}
}
for(int i=1;i<=4;i++){
for(int j=1;j<=6;j++){
if(map[i][j]==1){
set(i+1,j);
}
}
}
bool flag=true;
for(int j=1;j<=6;j++){
if(map[5][j]==1){
flag=false;
break;
}
}
if(flag)break;
else reset();
}
for(int i=1;i<=5;i++){
for(int j=1;j<=6;j++){
cout<<ans[i][j]<<' ';
}
cout<<endl;
}
}

练习题

luogu P10449 费解的开关