95. 费解的开关
好难。确实费解
最暴力方法:
每个格子有开和关两种操作,最暴力的方法就是对每个格子进行这两种操作,所以最后的时间复杂度是225 = 三千多万,每个开关有长度为5的for循环修改自己和上下左右四个方向,所以一共是一亿两千多万,会超时。
由题意可知:
- 顺序可以任意。即先按那个格子,最后都可以得到全为亮的结果。
- 每个格子最多只按一次。
- 设第i行已经确定,则要更改i+1行,只能通过修改i+2行更改i+1行的0
第i行已经确定,此时要修改第i+1行的0:如果直接更改i+1行,则势必会影响第i行,所以只能通过改变i+2行改变i+1行。所以如果确定了第1行,剩下的全能推出来。所以只需要枚举第一行的所有情况,将第一行确定,然后剩下i+1行的修改与否都可以通过i行j列是否为0来修改。
解题思路:
1. 如何枚举第一行操作
第一行有32种状态,分别对应5个位置。
这里用到了位运算的性质: 假设1表示翻转,0表示不动
设第一行的状态为 00000 则表示全不动,直接进行下面的i行的修改通过i+1行的修改确定。
设第一行的状态是11010 则表示第1 2 4 个灯需要翻转,对第一行第1 2 4列的灯进行翻转turn操作,然后再进行下面的第i行通过修改i+1行的修改确定。
2. 如何设置翻转自身和上下左右四个方向的turn函数
设置偏移量
int fx[5] = {0,-1,0,1,0};
int fy[5] = {0,0,-1,0,1};
for(int i=0;i<5;i++)
x = x + fx[i];
y = y + fy[i];
3. 时间复杂度
最坏情况下,第一行32种情况,然后一共修改了最多25个格子,每修改一个格子会牵扯5个格子,然后一共有500次询问,则最坏情况下的时间复杂度为32255*500 = 2 000 000 才两百万,足够了。
知识点
- 学会偏移量的设置
- 字符1和字符0的转换:0的ascii是110000 1的ascii是110001,所以只需要和1异或就能实现字符0和字符1 的转换
- 一般坐标的画法是向下为x轴正方向,向右为y轴正方向。
- 看一个数的二进制表示的第k位(位数从0开始)0还是1的方法:
假设数为i ,i >>k & 1得到的就是这个数二进制下第k位为0 or 1
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 6; //还有个 \0
char g[N][N],backup[N][N];
int fx[5] = {0,-1,0,1,0};
int fy[5] = {0,0,-1,0,1};
void turn(int x,int y)
{
for(int i=0;i<5;i++)
{
int a,b;
a=x+fx[i];
b=y+fy[i];
g[a][b] ^= 1;
}
}
int main()
{
int T;
cin>>T;
while (T--)
{
int ans = 10;
for(int i=0;i<5;i++)
cin>>g[i];
//枚举第一行操作
for(int op = 0; op < 32 ;op++)
{
int step = 0;
memcpy(backup,g,sizeof g ); //先把最初的备份起来,后面再还原
for(int i=0;i<5;i++)
{
if(op>>i & 1) //& 1表示取最低位
{
step++;
turn(0,i);
}
}
for(int i=0;i<4;i++)
for(int j=0;j<5;j++)
{
if(g[i][j] == '0')
{
step++;
turn(i+1,j);
}
}
bool dark = false;
for(int i=0;i<5;i++)
if(g[4][i]=='0')
{
dark = true;
break;
}
if(!dark) ans = min(ans,step);
memcpy(g, backup, sizeof g);
}
if(ans < 6) cout<<ans<<"\n";
else cout<<"-1\n";
}
}