算法竞赛进阶指南-29.飞行员兄弟
题目链接
“飞行员兄弟”这个游戏,需要玩家顺利的打开一个拥有 个把手的冰箱。
已知每个把手可以处于以下两种状态之一:打开或关闭。
只有当所有把手都打开时,冰箱才会打开。
把手可以表示为一个 的矩阵,您可以改变任何一个位置 上把手的状态。
但是,这也会使得第 行和第 列上的所有把手的状态也随着改变。
请你求出打开冰箱所需的切换把手的次数最小值是多少。
输入格式
输入一共包含四行,每行包含四个把手的初始状态。
符号 +
表示把手处于闭合状态,而符号 -
表示把手处于打开状态。
至少一个手柄的初始状态是关闭的。
输出格式
第一行输出一个整数 ,表示所需的最小切换把手次数。
接下来 行描述切换顺序,每行输出两个整数,代表被切换状态的把手的行号和列号,数字之间用空格隔开。
注意:如果存在多种打开冰箱的方式,则按照优先级整体从上到下,同行从左到右打开。
数据范围
输入样例:
-+--
----
----
-+--
输出样例:
6
1 1
1 3
1 4
4 1
4 3
4 4
Method : 位运算+枚举
这道题比较像第8题费解的开关,而本题的枚举状态更是简单。
解题要领:
按题目逻辑模拟实现开关函数
turn(int x, int y)
这种开关问题,如果按同一个开关两次,会恢复成原来的状态,因此要想按的次数最少,显然每个开关最多按一次,因此可以把状态进行二进制压缩:用一个int型数存储所有可能的开关方式,其二进制数表示开关的位置,
i * n + j
位数的1或0,映射到二维坐标[i][j]
上,决定开或关- 用
state
存储答案,(state >> n & 1 == 1)
来判断state从低到高的第n位是否为1 - 每次枚举前都要memcpy备份,枚举后都要memcpy还原
- 用
#include <iostream>
#include <cstring>
using namespace std;
const int n = 4;
const int INF = 0x3f3f3f3f;
char graph[n][n];
void change(char &c) {
if (c == '+') c = '-';
else c = '+';
}
void turn(int x, int y) {
change(graph[x][y]);
for (int i = 0; i < n; i ++) {
change(graph[i][y]);
}
for (int j = 0; j < n; j ++) {
change(graph[x][j]);
}
}
int main() {
for (int i = 0; i < n; i ++) {
cin >> graph[i];
}
int res = INF;
int state;
// 枚举所有开关打开的可能性, 1对应的位置选择turn
for (int op = 0; op < (1 << n * n); op ++) {
char backup[n][n];
memcpy(backup, graph, sizeof graph);
int cnt = 0;
for (int i = 0; i < n; i ++) {
for (int j = 0; j < n; j ++) {
if ((op >> (i * n + j)) & 1) {
turn(i, j);
cnt ++;
}
}
}
bool flag = true;
for (int i = 0; i < n; i ++) {
for (int j = 0; j < n; j ++) {
if (graph[i][j] == '+') {
flag = false;
break;
}
}
}
if (flag && cnt < res) {
res = cnt;
state = op;
}
memcpy(graph, backup, sizeof backup);
}
cout << res << endl;
for (int i = 0; i < n; i ++) {
for (int j = 0; j < n; j ++) {
if ((state >> (i * n + j)) & 1) {
printf("%d %d\n", i + 1, j + 1);
}
}
}
return 0;
}
复杂度分析
时间复杂度:, 第一个是状态的枚举,第二个是内部双重循环,最后一个是函数。
空间复杂度:,存储输入的图为。
本博客所有文章均采用 CC BY-NC-SA 4.0 协议 ,禁止商用,转载请注明出处!