正题
大意
有5*5个开关,每次选择一个地方时它和它上下左右的开关都会取反,求将所有开关都变成1的最少次数。
解题思路
首先我们知道一个位置一定不会点击超过一次。这样我们就可以得出一条性质,当第一行都已经决定是否点击过后,第一行就只能由第二行点击,那么这时第二行只能点击第一行还没打开的灯的下面,这样以此类推的话第二条性质就出来了。
如果第一行决定是否点击的方案了,如果有解的话,那么只有一种点击方式。
这样的话我们就可以枚举第一行每格是否点击,然后我们就可以推出后面几行,如果有解的话那么就取最小解。
代码
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int n=5;
int s[7],bs[7],ans,mins,t;
char a;
int main()
{
scanf("%d",&t);
for (int ti=1;ti<=t;ti++)
{
for(int i=1;i<=n;i++)
{
bs[i]=0;
for(int j=1;j<=n;j++)
{
cin>>a;
a-='0';
bs[i]=(bs[i]<<1)+a;//读入
}
}
mins=2147483647;
for (int i=0;i<=31;i++)//枚举
{
ans=0;
for (int j=1;j<=n;j++)
s[j]=bs[j];
for (int j=0;j<=n;j++)
if (i&(1<<j))//是否点击
{
if (j!=n-1)s[1]^=1<<(j+1);
s[1]^=1<<j;
if (j!=0)s[1]^=1<<(j-1);
s[2]^=1<<j;
ans++;
}//改变和统计
for (int j=2;j<=n;j++)
for (int k=0;k<n;k++)
if (!((s[j-1]>>k)&1))//上一行是否还有
{
if (k!=n-1)s[j]^=1<<(k+1);
s[j]^=1<<k;
if (k!=0)s[j]^=1<<(k-1);
s[j+1]^=1<<k;
ans++;
}//改变和统计
if (s[n]==31)//有解
mins=min(mins,ans);//取最小答案
}
if (mins>6) printf("-1\n");
else printf("%d\n",mins);
}
}