题目描述
现有n盏灯,以及m个按钮。每个按钮可以同时控制这n盏灯——按下了第i个按钮,对于所有的灯都有一个效果。按下i按钮对于第j盏灯,是下面3中效果之一:如果a[i][j]为1,那么当这盏灯开了的时候,把它关上,否则不管;如果为-1的话,如果这盏灯是关的,那么把它打开,否则也不管;如果是0,无论这灯是否开,都不管。
现在这些灯都是开的,给出所有开关对所有灯的控制效果,求问最少要按几下按钮才能全部关掉。
输入格式:
前两行两个数,n m
接下来m行,每行n个数,a[i][j]表示第i个开关对第j个灯的效果。
输出格式:
一个整数,表示最少按按钮次数。如果没有任何办法使其全部关闭,输出-1
输入样例:
3
2
1 0 1
-1 1 0
输出样例#1:
2
说明
对于20%数据,输出无解可以得分。
对于20%数据,n<=5
对于20%数据,m<=20
上面的数据点可能会重叠。
对于100%数据 n<=10,m<=100
题目分析
关灯问题——状态压缩经典
所谓状态压缩
就是将问题可能遇到的每一个状态用一个唯一的二进制数表示
其复杂度一般都是指数级的
这也注定了状压类的题数据规模都不会太大
此题中我们以1表示开灯状态,0表示关灯状态
这样我们可以以一个长度为n的二进制数唯一的表示每个状态
接着就可以依靠既定的开关关系将每个状态连接起来
即将隐式图转化为显式图通过BFS最短路解决
以灯全开状态为起点
二进制为n个1 ,十进制表示为(1<< n)-1
开始枚举m个开关以得到接下来的m个状态
for(int i=1;i<=m;i++)
{
ss=(1<< n)-1;
for(int j=1;j<=n;j++)
{
if( a[i][j]==1 && (ss&(1<<j-1)) ) ss^=(1<<j-1);
else if( a[i][j]==-1 && !(ss&(1<<j-1)) ) ss|=(1<<j-1);
}
}
ss&(1<< j-1)此运算用以检查当前状态的第j位是否为1
1<< j-1意思是将1左移j-1位,移动后只有第j位上是1
若开关操作为1且当前状态第j位是1
则ss^=(1<< j-1) 表示 1与1异或后得0
若开关操作为-1且当前状态第j位是0
则ss|=(1 << j-1) 表示 1或0 后得1 (此处异或操作也对,因为1异或0得1)
之后便用相同的方法在得到的每个状态上再不断搜索每个状态
由于是BFS,所以一旦某一步状态表示为0(十进制二进制的0写法都是0)
则当前步数必定是最短操作数
若遍历完所有状态都没有0,则输出-1
记得搜索过的状态要打vis标记避免重复访问
#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
int read()
{
int f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
void print(int x)
{
if(x<0){putchar('-');x=-x;}
if(x>9)print(x/10);
putchar(x%10+'0');
}
int n,m;
int a[110][1010];
struct node{int s,step;};//s存储状态,step存储当前步数
bool vis[1000010];
int spfa()
{
int ss;
queue<node> q;
q.push( (node) {(1<<n)-1,0} );
vis[(1<<n)-1]=true;//以全开为初始状态,二进制为n个1,十进制如代码
while(!q.empty())
{
node u=q.front();
q.pop();
if(u.s==0){return u.step;}//若状态为0,则返回当前步数
for(int i=1;i<=m;i++)
{
ss=u.s;
for(int j=1;j<=n;j++)//由于开关对所有灯都影响,所以每个灯都遍历
{
if( a[i][j]==1 && (ss&(1<<j-1)) ) ss^=(1<<j-1);
else if( a[i][j]==-1 && !(ss&(1<<j-1)) ) ss|=(1<<j-1);
}//位运算解释如上文本
if(!vis[ss])//若该状态未访问,就加入队列
{
q.push( (node) {ss,u.step+1} );
vis[ss]=true;
}
}
}
return -1;
}
int main()
{
n=read();m=read();
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
a[i][j]=read();//保存每个开关操作对灯的影响
print(spfa());
return 0;
}