思路
(1)枚举对第一行的所有操作
(2)操作完第n-1行后,根据第n-1行操作后的状态决定第n行的操作(第n-1暗的位置所对应下面的第n行同列位置要进行一次亮灭操作;第n-1行亮的位置所对应下面第n行同列位置不进行任何亮灭操作)
(3)所有行操作完成后,可以保证前n-1行是全亮,需要检查第n行是否全亮来判断游戏是否有解
性质
(1)无序性:无论亮灭格子的顺序如何(先/后操作某一行的某一格),对游戏结果没有影响
(2)最少步骤:每个位置至多进行一次亮灭操作,只有保留/改变原状态两种选择
技巧
(1)枚举第一行的所有操作有两种思路:一是指数型枚举,每个格子有保留/改变原状态两种选择;二是通过for循环(0~31的二进制表示分别唯一对应一种操作)
(2)判断数i二进制表示的第k位是否为1:i>>(k-1)&1
(3)结合偏移量数组实现区域化操作:对于二维4个偏移量(上下左右),手写一遍x、y方向的数组;对于8个偏移量的情况,分两类,一是上下左右、左上左下、右上右下,写两重for循环(i、j从-1~1),特判一下中心(0,0)的情况,二是国际象棋马跳日,手写一遍x、y方向的数组
(4)字符(0,1未知)变字符1/0的操作方法:一是if判断后取反;二是直接异或1/0
(5)读取字符数组的简便操作:以字符串的形式读取(注意最好预留一个位置存放’\0’,作为字符串结束终止符,确保字符数组可以当作字符串被其他函数处理)
时间复杂度分析
$32(第一行有32种操作方案)\times25(每行最多进行25次亮灭操作,即每格都操作)\times5(总共五行)\times500(至多500组数据)=2\times10^6$
代码
#include <bits/stdc++.h>
using namespace std;
int n;
char st[6][6],backup[6][6]; //多开一位 接收'\0'
int dx[5] = {0,-1,0,1,0},dy[5] = {-1,0,1,0,0}; //方向向量数组
void turn(int x,int y)
{
for(int i = 0;i < 5;i++)
{
int a = x+dx[i],b = y+dy[i];
if(a<0||a>4||b<0||b>4) continue; //超出边界 直接忽略
//未超边界 状态取反
st[a][b] ^= 1;
}
}
int main()
{
cin>>n;
while(n--)
{
//读入一组游戏数据
for(int i =0;i < 5;i++) cin>>st[i];
//存储该组游戏的结果
int res = 10;
//备份原始状态
memcpy(backup,st,sizeof(st));
//每组游戏第一行均有2^5=32种操作 对应0~31的二进制表示
for(int op = 0;op < 32;op++)
{
//当前这种操作所对应的步骤数
int step =0;
for(int i = 0;i < 5;i++)
{
//op为1 说明要进行一次翻转
if(op>>i&1)
{
//每翻转一次 步骤数加一
step++;
turn(0,4-i);
}
}
//处理完第一行后 剩下每一行对应的操作由上一行的状态决定
for(int i = 0;i < 4;i++)
{
for(int j = 0;j < 5;j++)
{
//上一行操作完后为暗的地方 下一行同列位置要翻转一次
//上一行操作完后为亮的地方 下一行同列位置无需操作
if(st[i][j] == '0')
{
step++;
turn(i+1,j);
}
}
}
//如上操作完后 前4行一定为全亮 检查最后一行的状态是否满足全亮
bool dark = false;
for(int i = 0;i < 5;i++)
{
if(st[4][i] == '0')
{
dark = true;
break;
}
}
if(!dark) res = min(res,step);
memcpy(st,backup,sizeof(backup));
}
if(res>6) cout<<-1<<endl;
else cout<<res<<endl;
}
return 0;
}