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
这是一个很有趣的游戏,可能很多人都玩过。每一次玩的时候都是要不断地进行尝试,我们人的大脑或许可以在特定的情况下得到比较准确地判断。但是要是这个问题交给计算机去完成的话,它可能就只能老老实实地去搜索了。如果完全暴力的搜索的话,假设矩阵式N*M的话时间复杂度是2^N*M,显然是很大的。算法实现不难但是这种方法还是很有限的。
通过研究这个游戏我们可以得到几个规律:
1,若存在一个可行解使得最后所有灯的状态都是熄灭的话,这些操作的顺序不会影响最后的结果。所以我们最后可以用一个bool矩阵表示解。
2,每一个灯至多只需要进行一次操作(熄灭或者点亮),因为偶数次操作和不操作是等价的,而基数次操作和操作一次是等价的。
我们从另一个角度去思考,如果第一行的操作已经决定的情况下
,
为了保证所有灯的状态保持是灭的话,第二行的操作就可以根据第一行来确定,第三行可以根据第二行来确定。只要最后恰好所有灯都灭了的话,就是我们所要找的解。所以我们只要枚举第一行的所有情况,然后根据第一行的结果推出后面所有的操作,最后判断是否为可行解就行了。这一下,时间复杂度变成了2^N.在本题的规模情况下还是能很快的解决的。算法实现的代码如下:
#include
int l[7] , a[7][7] ,t[7][7] , b[7][7]
;//l是记录第一行的排列组合情况
int f = 0 , n = 5 , m = 6 ;
void cpy(){//每一次搜索前将原数组a
copy到temp数组
for(int i = 1 ; i <= n ;
i++)
for(int j = 1 ; j <= m ;
j++)
t[i][j] = a[i][j] ;
}
int ok(int i ,int
j){//判断下表是否越界
if(i >=1
&& i <=n
&& j >= 1
&& j
<=m)
return 1 ;
else return 0 ;
}
void mark(int i , int
j){//点灯的操作
t[i][j] = ! t[i][j] ;
if(ok(i-1 , j)) t[i-1][j] = ! t[i-1][j]
;
if(ok(i+1 , j)) t[i+1][j] = ! t[i+1][j]
;
if(ok(i , j-1)) t[i][j-1] = ! t[i][j-1]
;
if(ok(i , j+1)) t[i][j+1] = ! t[i][j+1]
;
}
void dis(int
ta[7][7]){//打印一个数组的内容
for(int i = 1 ; i <= n ;
i++)
{
for(int j = 1 ; j <= m;
j++)
{
if(j != 1)
printf(" ")
;
printf("%d" , ta[i][j]) ;
}
printf("\n") ;
}
}
int
af(){//判断操作完成后结果是否为全部为0(事实上只判断最后一行因为之前我 //们的操作已经保证都为0了)的函数
for(int i = 1 ; i <= m ;
i++)
if(t[n][i]) return 0 ;
return 1 ;
}
void sear(){//搜索的函数
for(int i = 1 ; i <= m ;
i++)
{
if(l[i])
mark(1 , i) ;
b[1][i] = l[i] ;
}
for(int i = 2 ; i <= n ;
i++)
for(int j = 1 ; j <= m ;
j++)
if(t[i-1][j]) mark(i , j) ,
b[i][j] = 1 ;
else b[i][j] = 0 ;
}
int ml(int cur){//生成组合
if(f)
return 1 ;
if(cur == m + 1)
{
cpy() ;
sear() ;
if(af()) f = 1 ;
}
else
for(int i = 0 ; i < 2 ;
i++)
l[cur] = i , ml( cur + 1 ) ;
return 1 ;
}
int main(){
for(int i = 1 ; i <= n ;
i++)
for(int j = 1 ; j <= m ;
j++)
scanf("%d" , &a[i][j])
;
if(ml(1)) dis(b) ;
return 0 ;
}