题目描述
现有n盏灯,以及m个按钮。每个按钮可以同时控制这n盏灯——按下了第i个按钮,对于所有的灯都有一个效果。按下i按钮对于第j盏灯,是下面3中效果之一:如果a[i][j]为1,那么当这盏灯开了的时候,把它关上,否则不管;如果为-1的话,如果这盏灯是关的,那么把它打开,否则也不管;如果是0,无论这灯是否开,都不管。
现在这些灯都是开的,给出所有开关对所有灯的控制效果,求问最少要按几下按钮才能全部关掉。
输入输出格式
输入格式:
前两行两个数,n m
接下来m行,每行n个数,a[i][j]表示第i个开关对第j个灯的效果。
输出格式:
一个整数,表示最少按按钮次数。如果没有任何办法使其全部关闭,输出-1
输入输出样例
输入样例#1: 复制
3
2
1 0 1
-1 1 0
输出样例#1: 复制
2
说明
对于20%数据,输出无解可以得分。
对于20%数据,n<=5
对于20%数据,m<=20
上面的数据点可能会重叠。
对于100%数据 n<=10,m<=100
使用DP 将问题所列的状态用二进制数来表示
1 表示灯开
0 表示灯关
枚举m个开关的状态
以灯全开为起点进行枚举操作
<手打可能有误
异或,与,或,好重要~(UP UP UP
for(int i=1;i<=m;i++){
ss=(1<<n)-1 ;//表示二进制数的n个1的情况
for(int j=1;j<=n;j++){
if(cz[i][j]==1&&(ss&(1<<(j-1)))) ss^=(1<<(j-1));
//如果此时的第j盏灯为1,操作关;
if(cz[i][j]==-1&&!(ss&(1<<(j-1))) ss|=(1<<(j-1)));
//。。。。为-1 操作开
}
}
来源~~ 感谢https://www.luogu.org/problemnew/solution/P2622
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<cstdio>
#include<queue>
#include<cstring>
using namespace std;
int n,m,cz[150][20];
int vis[1<<11];
struct node{
int s,step;
};
int bfs(){
queue<node> q;
q.push(node{(1<<n)-1,0}) ;
vis[(1<<n)-1]=1;
int i,j;
while(!q.empty() ){
node now=q.front() ;
q.pop() ;
if(now.s ==0){
return now.step ;
}
int k;
for(i=1;i<=m;i++){
k=now.s ;
for(j=1;j<=n;j++){
if(cz[i][j]==1&&(k&(1<<(j-1)))) k^=(1<<(j-1));
else if(cz[i][j]==-1&&!(k&(1<<(j-1)))) k|=(1<<(j-1));
}
if(!vis[k]){
vis[k]=1;
q.push(node{k,now.step +1});
}
}
}
return -1;
}
int main(){
scanf("%d%d",&n,&m);
int i,j;
for(i=1;i<=m;i++){
for(j=1;j<=n;j++){
scanf("%d",&cz[i][j]);
}
}
printf("%d",bfs());
}
第一次写DP类的BFS ~
加油panda