前言:这道题当然可以枚举去写,但那样代码就太复杂了,跟用位运算写差不多,不如直接用位运算解决。 这算是一道位运算的入门题,让你理解一些位运算(!!!位运算一定要注意运算优先级,打上括号!!!),并且其中一些关于位运算的算法很巧妙,所以可能文字难以表达得好

题目: 入门版:https://ac.nowcoder.com/acm/contest/20960/1018

进阶版(转换一下思维即可):https://ac.nowcoder.com/acm/contest/20960/1019

入门版思路:由于读入的数据只有两种状态,我们可以考虑使用01串+位运算实现。新建一个数组来记录每一个01串,然后按位读入if(t=='1'){a[i] |= 1<<j;}//+=也行,如果要求实现把所有灯都打开,只要加一个数组,else {b[i] |= 1<<j;},开关只不过是两种状态,记录时可以相互转化,这种思想有点巧。接着枚举对第一个01串的操作,然后以此类推,下一行关上一行的灯即可(与题解7的思想类似),如果关到最后一行,最后一行全为0了,那么就成功了,如果枚举完所有对第一个01串的操作还没有成功,就失败了。

代码:

#include<iostream>
using namespace std;
const int wid=4;
const int len=4;
int a[wid+5]={0};
char t;

bool deal(int t [])
{     
    
    int change[wid+5]={0};
    int cur[wid+5]={0};
    for(change[0]=0;change[0]<=(1<<wid)-1;change[0]++)
    {
        cur[0]=t[0] ^ change[0] ^ (change[0]>>1) ^ ( (change[0]<<1) & ( (1<<wid) -1) );//对第一行操作后第一行的结果
        cur[1]=t[1]^change[0];//对第一行操作后,对第二行的影响
        for(int k=1;k<wid;k++)
        {
            change[k]=cur[k-1];//对某行的操作取决于前一行灯目前的状态
            cur[k]^=change[k] ^ (change[k]>>1) ^ ( (change[k]<<1) & ((1<<wid)-1) );//对第k行操作
            cur[k+1]=t[k+1]^change[k];//对第k行操作后,对第k+1行的影响
        }
        if (cur[wid-1]==0) return true;
    }
    return false;
}
int main()
{    
    ios::sync_with_stdio(false);cin.tie(nullptr);
    for(int i=0;i<len;i++)
    {
        for(int j=0;j<wid;j++)
        {
            cin>>t;//cout<<t<<' ';
            if(t=='1'){a[i] |= 1<<j;}//+=也行
            //cout<<a[i]<<' ';
        }
    }
    if( deal(a)) {cout<<"YES";}
    else {cout<<"NO";}
    
    return 0;
}

注:这里的wid即每一行的宽度(列数),len即每一列的长度(行数)

进阶版思路小升级:行数最多为10,枚举行数最多只要2^^10次,而枚举列数最多2^^100次。故我们考虑把头转过90°看这个灯泡集群,即行与列互换。聪明的你会想只要把代码中所有代表行(数)的变量(len)改成代表列(数)的变量(wid)即可,然而这还不够简单。我们只要在输入时,把行数当列数,列数当行数输入,就直接实现了上述功能。所以我进阶版代码就从入门版复制粘贴,再加了句输入,把常量改变量,就AC了,成为我目前最快AC的一道题(大概30多秒)

进阶版代码(只修改了三处,有点重复了):

#include<iostream>
using namespace std;
int wid;
int len;
int a[105]={0};
char t;

bool deal(int t [])
{     
    
    int change[105]={0};
    int cur[105]={0};
    for(change[0]=0;change[0]<=(1<<wid)-1;change[0]++)
    {
        cur[0]=t[0] ^ change[0] ^ (change[0]>>1) ^ ( (change[0]<<1) & ( (1<<wid) -1) );//对第一行操作后第一行的结果
        cur[1]=t[1]^change[0];//对第一行操作后,对第二行的影响
        for(int k=1;k<wid;k++)
        {
            change[k]=cur[k-1];//对某行的操作取决于前一行灯目前的状态
            cur[k]^=change[k] ^ (change[k]>>1) ^ ( (change[k]<<1) & ((1<<wid)-1) );//对第k行操作
            cur[k+1]=t[k+1]^change[k];//对第k行操作后,对第k+1行的影响
        }
        if (cur[wid-1]==0) return true;
    }
    return false;
}
int main()
{    
    ios::sync_with_stdio(false);cin.tie(nullptr);
    cin>>wid>>len;
    for(int i=0;i<len;i++)
    {
        for(int j=0;j<wid;j++)
        {
            cin>>t;//cout<<t<<' ';
            if(t=='1'){a[i] |= 1<<j;}//+=也行
            //cout<<a[i]<<' ';
        }
    }
    if( deal(a)) {cout<<"YES";}
    else {cout<<"NO";}
    
    return 0;
}

拓展:如果要实现算出最少要翻转的次数,应该怎么办?遍历change数组,把每个01串中1的个数算出来,最后再加起来,不妨自己尝试一下

注:算出01串中1的个数的方法

int sum=0,res=0;
for (int i=0;i<wid;i++)
{
  for(int j=0;j<len;j++)
  {
    sum+=(a[i] & 1);//判断最后一位有没有1
    a[i]>>1;//扔掉最后一位,使它旁边的那位成为新的最后一位
  }
}
res=min(res,sum);