正题
题目链接:https://vijos.org/p/1197
大意
有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);}
}