vijos1197-费解的开关【递推,枚举,位运算】

正题

题目链接:https://vijos.org/p/1197


大意

有5*5个开关,每次选择一个地方时它和它上下左右的开关都会取反,求将所有开关都变成1的最少次数。


解题思路

首先我们知道一个位置一定不会点击超过一次。这样我们就可以得出一条性质,当第一行都已经决定是否点击过后,第一行就只能由第二行点击,那么这时第二行只能点击第一行还没打开的灯的下面,这样以此类推的话第二条性质就出来了。
如果第一行决定是否点击的方案了,如果有解的话,那么只有一种点击方式。
这样的话我们就可以枚举第一行每格是否点击,然后我们就可以推出后面几行,如果有解的话那么就取最小解。


代码

#include
#include
#include
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<//是否点击
          {
            if (j!=n-1)s[1]^=1<<(j+1);
            s[1]^=1<if (j!=0)s[1]^=1<<(j-1);
            s[2]^=1<//改变和统计
        for (int j=2;j<=n;j++)
          for (int k=0;kif (!((s[j-1]>>k)&1))//上一行是否还有
            {
                if (k!=n-1)s[j]^=1<<(k+1);
                s[j]^=1<if (k!=0)s[j]^=1<<(k-1);
                s[j+1]^=1<//改变和统计
        if (s[n]==31)//有解
        mins=min(mins,ans);//取最小答案
    }
    if (mins>6) printf("-1\n");
    else printf("%d\n",mins);
    }
}

你可能感兴趣的:(其他,优雅的暴力)