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