Luogu-P2622-关灯问题II

题目 P2622 关灯问题II

题目描述

现有 nn 盏灯,以及 mm 个按钮。每个按钮可以同时控制这 nn 盏灯——按下了第 ii 个按钮,对于所有的灯都有一个效果。按下 ii 按钮对于第 jj 盏灯,是下面 33 中效果之一:如果 ai,ja_{i,j}11,那么当这盏灯开了的时候,把它关上,否则不管;如果为 1-1 的话,如果这盏灯是关的,那么把它打开,否则也不管;如果是 00,无论这灯是否开,都不管。

现在这些灯都是开的,给出所有开关对所有灯的控制效果,求问最少要按几下按钮才能全部关掉。

输入格式

前两行两个数,n,mn, m

接下来 mm 行,每行 nn 个数 ,ai,j,a_{i, j} 表示第 ii 个开关对第 jj 个灯的效果。

输出格式

一个整数,表示最少按按钮次数。如果没有任何办法使其全部关闭,输出 1-1

思路:状态压缩bfs

因为 n10n \leq 10,所以可以用一个数字表示灯的开关状态。

基本定义

statestate 表示当前灯的开关状态。

如果第 ii 个灯开启,那么 statestate(二进制)中的第 ii 位为 11(以最右边为第 00 位)。

使用 vis[state]vis[state] 记录 statestate 状态是否访问过。

按钮效果处理

  • 1:xxand01: x \rightarrow x \mathrm{and} 0
  • 0:xx0: x \rightarrow x
  • 1:xxor1-1: x \rightarrow x \mathrm{or} 1

AC Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// template v7
#include <bits/stdc++.h>

using namespace std;
using ll = long long;

inline void minset(int &t, int other) {
t = min(t, other);
}

inline void maxset(int &t, int other) {
t = max(t, other);
}

const int inf = 0x7f7f7f7f;

const int N = (1 << 10) + 10;
const int M = 110;

int step[N];
bool vis[N];

int op[M][11];
int n, m;

int bfs() {
int full = (1 << n) - 1;
queue<int> q;
q.push(full);
vis[full] = true;
while(q.size()) {
int from = q.front();
q.pop();
for(int i = 1; i <= m; i++) {
int state = from;
for(int j = n - 1; j >= 0; j--) {
int op_num = op[i][j];
switch(op_num) {
case -1:
state |= (1 << j);
break;
case 1:
state &= (full ^ (1 << j));
}
}
if(!vis[state]) {
q.push(state);
vis[state] = true;
step[state] = step[from] + 1;
}
if(!state) {
return step[state];
}
}
}
return -1;
}

int main() {
cin >> n >> m;
for(int i = 1; i <= m; i++) {
for(int j = n - 1; j >= 0; j--) {
cin >> op[i][j];
}
}
cout << bfs() << endl;
return 0;
}

补充

既然是状态压缩,为什么不用状压dp?

考虑时间复杂度:

  • nn 个灯泡,状态数为 2n2^n
  • mm 种操作,因为灯泡的亮暗与操作顺序有关,所以需要枚举所有状态的全排列,约 m!m! 种。
  • 将它们相乘,时间复杂度为 O(2nm!)O(2^n \cdot m!)

所以,时间会炸,要用一些优化才行,但是这样反而比bfs复杂了。