#include
using namespace std;
const int N=10;
char a[N][N],backups[N][N];
int dx[5]={-1,0,1,0,0},dy[5]={0,0,0,1,-1};
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;
backups[a][b]^=1;
}
}
void solve()
{
for(int i=0;i<5;i++)
for(int j=0;j<5;j++)
cin>>a[i][j];
int res=10;
for(int op=0;op<(1<<5);op++)
{
int step=0;
memcpy(backups,a,sizeof a);
for(int i=0;i<5;i++)
if(!(op>>i&1))
{
step++;
turn(0,i);
}
for(int i=0;i<4;i++)
for(int j=0;j<5;j++)
if(backups[i][j]=='0')
{
step++;
turn(i+1,j);
}
bool success=true;
for(int i=0;i<5;i++)
if(backups[4][i]=='0')
success=false;
if(success)
res=min(res,step);
}
if(res>6)
res=-1;
cout<<res<<endl;
}
int main()
{
int t;
cin>>t;
while(t--)
solve();
return 0;
}
算是对现在的我来说比较有挑战性的一道题,完全理解该题的时候感觉非常爽,有一种通透的感觉
该题的意思是输入一个5*5
的矩阵,1
表示灯是亮的,0
表示灯是灭的,问最少经过多少次操作可以使得灯全部亮着,一次操作,该灯上下左右的灯都会受到影响,但是注意,上下左右的灯在该次操作时不会直接影响另外的灯
意思是说,每一次操作最多直接影响5
盏灯,我刚开始以为牵一发而动全身,其实没有那么复杂
1 < < 5 = 2 5 1<<5=2^5 1<<5=25
o p > > i ∧ 1 op>>i\land1 op>>i∧1表示的意思是移除 i i i位数字之后,末位的数字是多少(用二进制表示的数字的末尾)
把一盏灯状态修改,该灯的上下左右的灯全部变成和原来状态相反的情况,本来是亮的,现在变成熄灭,本来熄灭的,现在变成亮的
该题可以发现每一个位置最多被点击一次,如果可以被点击两次,相当于第一次点击白费了,反证法可以证明该观点成立
点击的前后顺序不影响答案,假设先点亮了某一盏灯,该灯周围的四盏灯都受到了影响,假设后点击该盏灯,有可能周围的四盏灯的状态确实和前面不一样,但是不影响最终的答案,答案只需要保证最后所有的灯都是亮的即可
上面的结论自己写还是比较难发现(至少对我来说)
第一行的情况固定之后,只有唯一一种方案进行操作,不管能不能使得灯全部变亮,第一行的数字只能由该数字下一行且同一列的数字来改变其状态,假设该数字是0
,那么只有改变该一行同一列的数字,使得该数字变成1
才有可能实现整个矩阵全部变成1
第一行有五个数字,每一个数字可以选择0
或者1
,有 2 5 2^5 25种选择,5
个数字,每个数字都是0
或者1
,所以可以用0
到31
的二进制表示第一行的所有数字,对于每一个输入的矩阵,第一行都可以有32
种情况,所以可以用上面代码中的操作,应该也可以直接对数组进行初始化,把第一行初始化为字符'1'
和字符'0'
试了一下,直接初始化不可行,因为第一行左右两个点之间会互相影响
第一个循环,如下这个循环,
for(int i=0;i<5;i++)
if(!(op>>i&1))
{
step++;
turn(0,i);
}
表示的是二进制表示开关的情况,啥意思呢,意思就是,按不按开关,其实和原来的矩阵是啥样没有关系,就是表示第一行每一个点按不按开关,!(op>>i&1)
加不加取反都是一样的,两种i情况的数目是一样多的,假设我们1
表示按开关的话,就不加取反,假设我们0
表示按开关的话,就加上取反
step
表示的是每一种方案操作的次数,turn
表示的是按开关会带来的影响
首先定义一个方向变量,表示上下左右中5
个方向,我比较喜欢左中右,上下这个顺序,所有x
的方向向量是-1,0,1,0,0
,y
的方向向量是0,0,0,1,-1
,然后遍历几个方向向量,表示新的点,如果越界了就不使用,没有越界就把数组的元素变成和当前元素不同的元素,比如说'1'
变成'0'
,实现该操作可以使用^
,经过异或操作,可以实现该结果
'1'
的ASCII
码是110001
,1
的ASCII
码是000001
,异或之后是110000
,刚好是'0'
的ASCII
码,比较巧妙
每一次操作都需要修改数组的元素,但是我们需要进行32
次操作,所以必须备份数组,保存原来的元素
所以可以使用一个备份数组,每一次都是使用备份数组进行操作
memcpy(backups,a,sizeof a);
该函数前面是备份数组,中间是来源数组,最后面是复制的长度
只要数组是'0'
表示下一行需要点击开关,总共五行,我们只需要遍历前面四行,因为每一次修改的都是该行的下一行
最后检查最后一行是否有'0'
,假设有'0'
表示没有实现要求