关灯问题II
题目描述
现有 n n n 盏灯,以及 m m m 个按钮。每个按钮可以同时控制这 n n n 盏灯——按下了第 i i i 个按钮,对于所有的灯都有一个效果。按下 i i i 按钮对于第 j j j 盏灯,是下面 3 3 3 中效果之一:如果 a i , j a_{i,j} ai,j 为 1 1 1,那么当这盏灯开了的时候,把它关上,否则不管;如果为 − 1 -1 −1 的话,如果这盏灯是关的,那么把它打开,否则也不管;如果是 0 0 0,无论这灯是否开,都不管。
现在这些灯都是开的,给出所有开关对所有灯的控制效果,求问最少要按几下按钮才能全部关掉。
输入格式
前两行两个数, n , m n, m n,m。
接下来 m m m 行,每行 n n n 个数 , a i , j ,a_{i, j} ,ai,j 表示第 i i i 个开关对第 j j j 个灯的效果。
输出格式
一个整数,表示最少按按钮次数。如果没有任何办法使其全部关闭,输出 − 1 -1 −1。
样例 #1
样例输入 #1
3
2
1 0 1
-1 1 0
样例输出 #1
2
提示
数据范围及约定
- 对于 20 % 20\% 20% 数据,输出无解可以得分。
- 对于 20 % 20\% 20% 数据, n ≤ 5 n \le 5 n≤5。
- 对于 20 % 20\% 20% 数据, m ≤ 20 m \le 20 m≤20。
上面的数据点可能会重叠。
对于 100 % 100\% 100% 数据 n ≤ 10 , m ≤ 100 n \le 10,m \le 100 n≤10,m≤100。
此题运用了状态压缩和
B
F
S
BFS
BFS 的结合。
并且注意本题是先输入列再输入行,因为此问题卡了
2
2
2 小时。
**同时也应注意:**加减法的优先级是高于左移和右移符号的。
使用二进制数来代表 n n n 个灯的亮灭,其中 1 1 1 代表亮, 0 0 0 代表灭。
在更新状态的时候,搜索当前灯对于其他灯的作用:
if (a[i][j] == 1 && (s >> j & 1)) s ^= (1 << j);
if (a[i][j] == -1 && !(s >> j & 1)) s |= (1 << j);
和普通 B F S BFS BFS 一样,发现没有搜过的状态就压入队列继续搜。
代码:
#include<iostream>
#include<queue>
using namespace std;
const int N = 12, M = 1 << 12;
int n, m;
int a[110][15];
bool vis[M];
int dis[M];
int bfs() {
queue<int>q;
q.push((1 << n) - 1);
dis[(1 << n) - 1] = 0;
vis[(1 << n) - 1] = 1;
while (q.size()) {
int t = q.front(); q.pop();
//cout << "State : " << t << " " << "Dis : " << dis[t] << endl;
if (t == 0) return dis[t];
for (int i = 0; i < m; i++) {
int s = t;
for (int j = 0; j < n; j++) {
if (a[i][j] == 1 && (s >> j & 1)) s ^= (1 << j);
else if (a[i][j] == -1 && !(s >> j & 1)) s |= (1 << j);
}
if (!vis[s]) {
q.push(s);
dis[s] = dis[t] + 1;
vis[s] = 1;
}
}
}
return -1;
}
int main() {
cin >> n >> m;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
cin >> a[i][j];
}
}
cout << bfs();
return 0;
}