[枚举] aw116. 飞行员兄弟(二维递推+开关问题+二进制枚举)
Posted Ypuyu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[枚举] aw116. 飞行员兄弟(二维递推+开关问题+二进制枚举)相关的知识,希望对你有一定的参考价值。
1. 题目来源
链接:116. 飞行员兄弟
区别题:[递推] aw95. 费解的开关(二维递推+开关问题+二进制枚举)
2. 题目解析
本题数据范围比较小,4*4 的棋盘,总共的状态数量是 2 4 ∗ 4 2^{4*4} 24∗4 种,也就是 65536 种,很少的状态数量。
本题不可以递推, 要区别于:[递推] aw95. 费解的开关(二维递推+开关问题+二进制枚举)。因为本题的状态改变是行列全部改变,故不会区别上一层、本层,就没办法去递推,互相影响的灯泡太多了,无法直接去递推。
但同时也满足两个性质。
- 每个点只会操作一次。
- 操作顺序是任意的。
这也是开关问题的一个共性了。
数据范围小,直接暴力枚举所有方案,因为每个开关只会有两种情况,按或不按,共 2 4 ∗ 4 2^{4*4} 24∗4 种,也就是 65536 种情况,最后判断当前枚举的这种方案能否将整个灯泡亮即可。
技巧:
- 方案要求按字典序最小输出,我们可以将二进制枚举的状态数对应到二维数组中,即移位的时候,让其每一位对应二维数组中的一个格子就行了。
- 当然也可以直接用位运算直接做
时间复杂度: O ( 2 16 ∗ ( 16 ∗ 7 + 16 + 16 ) ) = 9 , 437 , 184 O(2^{16}*(16*7+16+16))= 9,437,184 O(216∗(16∗7+16+16))=9,437,184,开关状态,每个点需要改变 7 个点(行列自己,共7个点),判断每个灯泡状态,记录方案。
空间复杂度: O ( 4 ∗ 4 ) O(4*4) O(4∗4)
二维数组写法:
#include <bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 5;
char g[N][N], backup[N][N];
int get(int x, int y) {
return x * 4 + y;
}
void turn_one(int x, int y) {
if (g[x][y] == '+') g[x][y] = '-';
else g[x][y] = '+';
}
void turn_all(int x, int y) {
for (int i = 0; i < 4; i ++ ) {
turn_one(x, i);
turn_one(i, y);
}
turn_one(x, y);
}
int main() {
for (int i = 0; i < 4; i ++ ) cin >> g[i];
vector<PII> res;
memcpy(backup, g, sizeof g); // 备份
for (int op = 0; op < 1 << 16; op ++ ) {
vector<PII> temp;
for (int i = 0; i < 4; i ++ ) // 进行操作,二进制位映射到二维数组
for (int j = 0; j < 4; j ++ )
if (op >> get(i, j) & 1) {
temp.push_back({i, j});
turn_all(i, j);
}
bool has_closed = false; // 判断所有灯泡是否全亮
for (int i = 0; i < 4; i ++ )
for (int j = 0; j < 4; j ++ )
if (g[i][j] == '+')
has_closed = true;
if (has_closed == false) { // 答案更新
if (res.empty() || res.size() > temp.size()) res = temp;
}
memcpy(g, backup, sizeof g); // 使用备份还原
}
cout << res.size() << endl;
for (auto op : res) cout << op.x + 1 << ' ' << op.y + 1 << endl;
return 0;
}
位运算写法:貌似对棋盘做了优化,看不太懂,也懒得看了。进阶指南的题,也是进阶指南的写法。change[N][N];
数组每一位存的是行列 7 个数的二进制表示之和。每次进行状态改变的时候,要改变 7 个位置的状态,就只需要 ^change[x][y]
就行了。
- 从0~(1<<16)枚举到的第一个直接输出也能ac,这是为啥?贪心?
- 本题比较特殊,可以用代码把所有情况枚举一遍,会发现每种局面的操作方案是唯一的,所以第一次找到的解一定是最优解。 从0~(1<<16)枚举到的第一个直接输出也能ac,这是为啥?贪心?
#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N = 4, INF = 100;
int change[N][N];
int get(int x, int y) {
return x * N + y;
}
int main() {
for (int i = 0; i < N; i ++ )
for (int j = 0; j < N; j ++ ) {
for (int k = 0; k < N; k ++ ) change[i][j] += (1 << get(i, k)) + (1 << get(k, j));
change[i][j] -= 1 << get(i, j);
}
int state = 0;
for (int i = 0; i < N; i ++ ) {
string line;
cin >> line;
for (int j = 0; j < N; j ++ )
if (line[j] == '+')
state += 1 << get(i, j);
}
vector<PII> path;
for (int i = 0; i < 1 << 16; i ++ ) {
int now = state;
vector<PII> temp;
for (int j = 0; j < 16; j ++ )
if (i >> j & 1) {
int x = j / 4, y = j % 4;
now ^= change[x][y];
temp.push_back({x, y});
}
if (!now && (path.empty() || path.size() > temp.size())) path = temp;
}
cout << path.size() << endl;
for (auto &p : path) cout << p.first + 1 << ' ' << p.second + 1 << endl;
return 0;
}
以上是关于[枚举] aw116. 飞行员兄弟(二维递推+开关问题+二进制枚举)的主要内容,如果未能解决你的问题,请参考以下文章
[递推] aw95. 费解的开关(二维递推+开关问题+二进制枚举)