题目传送门
题目描述:
现有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,其实是否是状压dp其实很好判断,你只要看一看数据范围就可以了,状压的数据范围一般不会超过30,只要数据范围过了关,再看一眼题目是否像状压dp就可以了。
接下来看怎么做:
我们先预处理出t[i][j]表示第i个开关开j状态后的状态。将每一个状态,每一个开关打开后所对应得状态预处理出来.
预处理部分如下:
for (int i=0;i<(1<<n);i++)
for (int j=1;j<=m;j++){
int ans=0;
for (int k=1;k<=n;k++){
if (a[j][k]==1) if (i&(1<<n-k)) ans=ans;else;//如果当前的操作是1,如果当前的灯是亮着的,把它关上,否则不管
else if (a[j][k]==-1) ans=ans+(1<<n-k);//如果当前的操作是-1,直接加上
else if (a[j][k]==0) if (i&(1<<n-k))ans+=(1<<n-k);else;else;//如果当前的操作是0,如果当前的灯是开着的,累加;否则不管
}
t[j][i]=ans;
}
预处理完之后,我们很容易知道题目的意思就是让我们求所有的灯都是0的状态,即让我们求0的时候的状态,因为开始时所有的灯都是开的,所以开始时的状态就是(1<<n)-1的状态。我们只要以(1<<n)-1为起点,0为终点做一遍BFS,记录步数即可
那么具体代码如下:
#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[110][110];
queue < int > q;
int t[2001][2001];
int dp[2001];
int main(){
scanf("%d %d",&n,&m);
for (int i=1;i<=m;i++)
for (int j=1;j<=n;j++) scanf("%d",&a[i][j]);
for (int i=0;i<(1<<n);i++)
for (int j=1;j<=m;j++){
int ans=0;
for (int k=1;k<=n;k++){
if (a[j][k]==1) if (i&(1<<n-k)) ans=ans;else;//如果当前的操作是1,如果当前的灯是亮着的,把它关上,否则不管
else if (a[j][k]==-1) ans=ans+(1<<n-k);//如果当前的操作是-1,直接加上
else if (a[j][k]==0) if (i&(1<<n-k))ans+=(1<<n-k);else;else;//如果当前的操作是0,如果当前的灯是开着的,累加;否则不管
}
t[j][i]=ans;
}
// for (int i=0;i<(1<<n);i++){
// for (int j=1;j<=m;j++) cout<<t[j][i]<<' ';
// cout<<endl;
// }
memset(dp,-1,sizeof(dp));
dp[(1<<n)-1]=0;
q.push((1<<n)-1);
while (!q.empty()){
int x=q.front();q.pop();
for (int i=1;i<=m;i++){
int y=t[i][x];
if (dp[y]==-1) dp[y]=dp[x]+1,q.push(y);
}
}
// for (int i=(1<<n)-1;i>=0;i--) cout<<dp[i]<<' ';
// cout<<endl;
printf("%d",dp[0]);
return 0;
}