#include
using namespace std;
const int N = 1e5 + 5;
int a[1 << 15];
int n, m;
queue<int> q;
int main() {
memset(a, -1, sizeof a);
cin >> n >> m;
while (n--) {
string s;
cin >> s;
int sum = 0;
for (int i = 0; i < m; ++i) {
sum = 2 * sum + s[i] - '0';
}
a[sum] = m;
q.push(sum);
}
int u, v;
while (q.size()) {
u = q.front();
q.pop();
for (int i = 0; i < m; ++i) {
// 我们发现可以对队首元素u每一位取反,这样就能产生比现在相似度小1的数字
v = u ^ (1 << i);
// 如果这个数字之前没出现过,就可以标记并入队
if (a[v] == -1) {
a[v] = a[u] - 1;
q.push(v);
}
}
}
// 如何找到最小数字?枚举会超时,但不难发现,BFS最后取出的那个数相似度必然最小
for (int i = m - 1; i >= 0; --i) {
cout << ((u >> i) & 1);
}
return 0;
}
显而易见,保存和更新状态就是BFS的核心,在不同类型的问题里,需要保存和更新状态也是不一样的,这节课我们就来看看如何使用BFS解决钥匙迷宫、动态迷宫和建立模型。
#include
using namespace std;
const int N = 205;
const int dir[2][4] = {1, 0, -1, 0, 0, 1, 0, -1};
struct node {
int x, y, z; // 其中z就是记录钥匙是否被取的整数
node() { }
node(int _x, int _y, int _z) {
x = _x;
y = _y;
z = _z;
}
};
int vis[N][N][1 << 5];
string s[N];
queue<node> q;
// 判断x的二进制中有多少个1,也就是目前持有多少把钥匙
inline int get(int x) {
int res = 0;
while (x) {
res += x & 1;
x >>= 1;
}
return res;
}
int n, m, k;
// 结果
int res;
// 存储钥匙位置
vector<pair<int, int> > key;
inline bool in(node A) {
return 0 <= A.x && A.x < n && 0 <= A.y && A.y < m;
}
#define a first
#define b second
void bfs(node st) {
q.push(st);
vis[st.x][st.y][st.z] = 0;
while (q.size() && res == -1) {
node u = q.front();
q.pop();
int c = vis[u.x][u.y][u.z];
for (int i = 0; i < 4; ++i) {
// 下一个点
node v;
v.x = u.x + dir[0][i];
v.y = u.y + dir[1][i];
v.z = u.z;
// 出格或者是墙的话
if (!in(v) || s[v.x][v.y] == '#') {
continue;
}
// 目前字符是宝石的话
if ('0' <= s[v.x][v.y] && s[v.x][v.y] <= '4') {
v.z |= 1 << (s[v.x][v.y] - '0');
}
// 访问过的话
if (vis[v.x][v.y][v.z] != -1) {
continue;
}
// 到终点并且有至少k把钥匙
if (s[v.x][v.y] == 'E' && get(v.z) >= k) {
res = c + 1;
break;
}
// 是钥匙就捡
if (s[v.x][v.y] == '$') {
for (int j = 0; j < key.size(); ++j) {
if (vis[key[j].a][key[j].b][v.z] == -1) {
vis[key[j].a][key[j].b][v.z] = c + 1;
q.push(node(key[j].a, key[j].b, v.z));
}
}
} else {
// 符合条件标记并入队
vis[v.x][v.y][v.z] = c + 1;
q.push(v);
}
}
}
}
int main() {
int T;
cin >> T;
while (T--) {
// 重置
memset(vis, -1, sizeof vis);
res = -1;
key.clear();
while (q.size()) {
q.pop();
}
cin >> n >> m >> k;
node st(0, 0, 0);
for (int i = 0; i < n; ++i) {
cin >> s[i];
for (int j = 0; j < m; ++j) {
// 起点
if (s[i][j] == 'S') {
st.x = i;
st.y = j;
}
// 钥匙
if (s[i][j] == '$') {
key.push_back(make_pair(i, j));
}
}
}
bfs(st);
if (res == -1) {
cout << "oop!\n";
} else {
cout << res << '\n';
}
}
return 0;
}
动态迷宫
定义:迷宫里有一些特殊的东东,这些东东的状态周期性地变化。比如,有一个梯子,最开始是横着的(—),但是过一分钟就会变成竖着的(|),人物只能沿着梯子移动。
我们发现,整个迷宫的周期是一致的,说明整个迷宫是有两种状态,也就是奇数个单位时间和偶数个单位时间的状态。
那么我们有什么策略呢?我们可以将每一个可以改变状态的位置分两个状态考虑,分别记录这个位置第一种状态和第二种状态时是否被访问过。当然,如果有k种状态,我们也可以同时记录k种状态。
对于这一道题,其实通过梯子时过不去的话最简单的方法就是等待,耗费1个单位时间。
代码
#include
using namespace std;
const int N = 25;
const int dir[2][4] = {-1, 1, 0, 0, 0, 0, -1, 1};
string s[N];
bool vis[N][N];
struct node {
// 位置
int x, y;
// 步数
int stp;
//是否停留
bool pause;
node() { }
node(int _x, int _y, int _stp, bool _pause) {
x = _x;
y = _y;
stp = _stp;
pause = _pause;
}
};
queue<node> q;
int n, m;
// 结果
int res = -1;
// 是否出格
inline bool in(node a) {
return 0 <= a.x && a.x < n && 0 <= a.y && a.y < m;
}
void bfs(node st) {
// 起点标记入队
q.push(node(st.x, st.y, 0, false));
vis[st.x][st.y] = true;
while (q.size() && res == -1) {
node u = q.front();
q.pop();
for (int i = 0; i < 4; ++i) {
// 下一个点
node v;
v.x = u.x + dir[0][i];
v.y = u.y + dir[1][i];
v.stp = u.stp + 1;
v.pause = false;
// 当前点是否访问
#define b vis[v.x][v.y]
// 出格或者访问过则尝试下一个点
if (!in(v) || b) {
continue;
}
// 当前点元素
#define c s[v.x][v.y]
// 如果当前是竖着的
if (c == '|') {
if ((i < 2 && u.stp % 2 == 0) || (i >= 2 && u.stp % 2 == 1)) {
v.x += dir[0][i];
v.y += dir[1][i];
if (b || c == '*') {
continue;
}
} else {
continue;
}
}
if (c == '-') { // 如果是横着的
if ((i < 2 && u.stp % 2 == 1) || (i >= 2 && u.stp % 2 == 0)) {
v.x += dir[0][i];
v.y += dir[1][i];
if (b || c == '*') {
continue;
}
} else {
continue;
}
}
// 是路的话(合法),标记入队
if (c == '.') {
q.push(v);
b = true;
}
// 到达终点
if (c == 'T') {
res = v.stp;
break;
}
}
// 不停留就标记入队
if (!u.pause) {
u.pause = true;
++u.stp;
q.push(u);
}
}
}
int main() {
cin >> n >> m;
node st;
for (int i = 0; i < n; ++i) {
cin >> s[i];
for (int j = 0; j < m; ++j) {
if (s[i][j] == 'S') {
st.x = i;
st.y = j;
}
}
}
bfs(st);
cout << res << '\n';
return 0;
}
有些问题,比如:[NOIP2008提高组]传纸条,就不能只考虑一次搜索,把第一次决策做到最好,不一定是最好的总决策,这种问题就要用到双向搜索了。
什么是双向搜索呢?双向搜索就是同时从两边开始搜索,每一步都要使目前两个决策总体最优,到最后会一起搜索到结果,此时的结果就是全局最优解了。