1 专题说明
本专题用来记录使用Flood Fill算法和最短路模型解决的搜索问题。
2 训练
题目1:1097池塘计数
C++代码如下,
方法1:使用bfs算法
#include <iostream>
#include <queue>
using namespace std;
const int N = 1010;
int n, m;
char g[N][N];
bool st[N][N];
void bfs(int i, int j) {
queue<pair<int,int>> q;
q.push(make_pair(i, j));
st[i][j] = true;
while (!q.empty()) {
pair<int,int> t = q.front();
q.pop();
int x = t.first, y = t.second;
for (int dx = -1; dx <= 1; ++dx) {
for (int dy = -1; dy <= 1; ++dy) {
if (dx == 0 && dy == 0) continue;
int nx = x + dx, ny = y + dy;
if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
if (st[nx][ny]) continue;
if (g[nx][ny] != 'W') continue;
q.push(make_pair(nx, ny));
st[nx][ny] = true;
}
}
}
return;
}
int main() {
cin >> n >> m;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
cin >> g[i][j];
}
}
int res = 0;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (g[i][j] == 'W' && st[i][j] == false) {
bfs(i, j);
res++;
}
}
}
cout << res << endl;
return 0;
}
方法2:使用并查集
#include <iostream>
#include <vector>
using namespace std;
const int N = 1010;
const int M = 1e6 + 10;
int n, m;
char g[N][N];
int p[M];
int cnt;
int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
void merge(int a, int b) {
int pa = find(a);
int pb = find(b);
if (pa == pb) return;
p[pa] = pb;
cnt--;
return;
}
int main() {
cin >> n >> m;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
cin >> g[i][j];
if (g[i][j] == 'W') cnt++;
}
}
//并查集初始化
for (int k = 0; k < n * m; ++k) {
p[k] = k;
}
int dirs[8][2] = {{-1,-1}, {-1,0}, {-1,1}, {0,-1}, {0,1}, {1,-1}, {1,0}, {1,1}};
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (g[i][j] == 'W') {
for (int k = 0; k < 8; ++k) {
int x = i + dirs[k][0];
int y = j + dirs[k][1];
if (x < 0 || x >= n || y < 0 || y >= m) continue;
if (g[x][y] != 'W') continue;
int a = i * m + j; //乘以列数
int b = x * m + y;
merge(a, b);
}
}
}
}
cout << cnt << endl;
return 0;
}
题目2:1098城堡问题
C++代码如下,
方法1:bfs
#include <iostream>
#include <queue>
using namespace std;
const int N = 60;
int n, m;
int g[N][N];
bool st[N][N];
int cnt = 0; //房间总数
int maxv = 0; //房间最大面积
void bfs(int i, int j) {
int dirs[4][2] = {{0,-1}, {-1,0}, {0,1}, {1,0}};
//1表示西墙,2表示北墙,4表示东墙,8表示南墙
queue<pair<int,int>> q;
q.push(make_pair(i,j));
st[i][j] = true;
int curv = 0; //当前房间面积
while (!q.empty()) {
pair<int,int> t = q.front();
q.pop();
curv ++;
int x = t.first, y = t.second;
for (int k = 0; k < 4; ++k) {
int nx = x + dirs[k][0], ny = y + dirs[k][1];
if ((g[x][y] >> k) & 1) continue; //有一堵墙
if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
if (st[nx][ny]) continue;
q.push(make_pair(nx,ny));
st[nx][ny] = true;
}
}
maxv = max(maxv, curv);
return;
}
int main() {
cin >> n >> m;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
cin >> g[i][j];
}
}
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (!st[i][j]) {
bfs(i, j);
cnt ++;
}
}
}
cout << cnt << endl << maxv << endl;
return 0;
}
方法2:并查集
#include <iostream>
using namespace std;
const int N = 60, M = 2500 + 10;
int n, m;
int g[N][N];
int p[M];
int cnt[M]; //每个并查集中的元素数目,只对根结点有效
int tot; //房间总数
int maxv; //房间最大面积
int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
void merge(int a, int b) {
int pa = find(a), pb = find(b);
if (pa == pb) return;
p[pa] = pb;
cnt[pb] += cnt[pa];
tot--;
maxv = max(maxv, cnt[pb]);
return;
}
int main() {
cin >> n >> m;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
cin >> g[i][j];
}
}
//初始化并查集
for (int k = 0; k < n * m; ++k) {
p[k] = k;
cnt[k] = 1;
}
tot = n * m;
maxv = 1;
int dirs[4][2] = {{0,-1}, {-1,0}, {0,1}, {1,0}};
//1表示西墙,2表示北墙,4表示东墙,8表示南墙
//遍历并查集
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
for (int k = 0; k < 4; ++k) {
int a = i * m + j;
int x = i + dirs[k][0], y = j + dirs[k][1];
if (x < 0 || x >= n || y < 0 || y >= m) continue;
if ((g[i][j] >> k) & 1) continue; //有一堵墙,跳过
int b = x * m + y;
merge(a, b);
}
}
}
cout << tot << endl << maxv << endl;
return 0;
}
题目3:1106山峰和山谷
C++代码如下,
#include <iostream>
#include <queue>
using namespace std;
const int N = 1010;
int n;
int g[N][N];
bool st[N][N];
int cnt_crest = 0; //峰顶数目
int cnt_valley = 0; //谷底数目
void bfs(int i, int j) {
queue<pair<int,int>> q;
q.push(make_pair(i,j));
st[i][j] = true;
bool tag1 = false; //出现其它元素大于集合内元素
bool tag2 = false; //出现其它元素小于集合内元素
while (!q.empty()) {
pair<int,int> t = q.front();
q.pop();
int x = t.first;
int y = t.second;
for (int dx = -1; dx <= 1; ++dx) {
for (int dy = -1; dy <= 1; ++dy) {
if (dx == 0 && dy == 0) continue;
int nx = x + dx;
int ny = y + dy;
if (nx < 0 || nx >= n || ny < 0 || ny >= n) continue;
if (g[nx][ny] > g[i][j]) {
tag1 = true;
} else if (g[nx][ny] < g[i][j]) {
tag2 = true;
}
if (st[nx][ny]) continue;
if (g[nx][ny] == g[i][j]) {
q.push(make_pair(nx, ny));
st[nx][ny] = true;
}
}
}
}
if (tag1 == false && tag2 == false) {
cnt_crest++;
cnt_valley++;
} else if (tag1 == true && tag2 == false) {
cnt_valley++;
} else if (tag1 == false && tag2 == true) {
cnt_crest++;
}
return;
}
int main() {
cin >> n;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
cin >> g[i][j];
}
}
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (!st[i][j]) {
bfs(i, j);
}
}
}
cout << cnt_crest << " " << cnt_valley << endl;
return 0;
}
题目4:1076迷宫问题
C++代码如下,
#include <iostream>
#include <queue>
#include <unordered_map>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 1010;
int n;
int g[N][N];
bool st[N][N];
unordered_map<int, int> map_node_prevnode;
int main() {
cin >> n;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
cin >> g[i][j];
}
}
queue<pair<int,int>> q;
q.push(make_pair(0,0));
st[0][0] = true;
int dirs[4][2] = {{-1,0}, {1,0}, {0,-1}, {0,1}};
while (!q.empty()) {
pair<int, int> t = q.front();
q.pop();
int x = t.first;
int y = t.second;
for (int k = 0; k < 4; ++k) {
int nx = x + dirs[k][0];
int ny = y + dirs[k][1];
if (nx < 0 || nx >= n || ny < 0 || ny >= n) continue;
if (st[nx][ny]) continue;
if (g[nx][ny] != 0) continue;
//可以走到(nx,ny)
int a = x * n + y;
int b = nx * n + ny;
map_node_prevnode[b] = a;
q.push(make_pair(nx,ny));
st[nx][ny] = true;
if (nx == n - 1 && ny == n - 1) break; //退出while循环
}
}
//输出路径
vector<pair<int,int>> paths = {make_pair(n-1, n-1)};
int node = n * n - 1;
while (node != 0) {
int prevnode = map_node_prevnode[node];
int prevx = prevnode / n;
int prevy = prevnode % n;
paths.emplace_back(prevx, prevy);
node = prevnode; //更新node
}
reverse(paths.begin(), paths.end());
for (auto [x, y] : paths) {
cout << x << " " << y << endl;
}
return 0;
}
题目5:188武士风度的牛
C++代码如下,
#include <iostream>
#include <queue>
using namespace std;
const int N = 160;
char g[N][N];
bool st[N][N];
int n, m;
pair<int, int> snode, enode;
int main() {
cin >> m >> n;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
cin >> g[i][j];
if (g[i][j] == 'K') snode = make_pair(i, j);
else if (g[i][j] == 'H') enode = make_pair(i, j);
}
}
queue<pair<int,int>> q;
q.push(snode);
st[snode.first][snode.second] = true;
int res = 0;
int dirs[8][2] = {{1,2}, {2,1}, {-1,2}, {-2,1}, {-1,-2}, {-2,-1}, {1,-2}, {2,-1}};
while (!q.empty()) {
res++;
int curn = q.size();
while (curn--) {
pair<int,int> t = q.front();
q.pop();
int x = t.first;
int y = t.second;
for (int k = 0; k < 8; ++k) {
int nx = x + dirs[k][0];
int ny = y + dirs[k][1];
if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
if (st[nx][ny]) continue;
if (g[nx][ny] == '*') continue;
q.push(make_pair(nx,ny));
st[nx][ny] = true;
if (make_pair(nx, ny) == enode) {
cout << res << endl;
return 0;
}
}
}
}
return 0;
}
题目6:1100抓住那头牛
C++代码如下,
方法1:bfs
#include <iostream>
#include <queue>
using namespace std;
const int N = 100010;
int a, b;
bool st[N];
int main() {
cin >> a >> b;
int res = 0;
queue<int> q;
q.push(a);
st[a] = true;
while (!q.empty()) {
int curn = q.size();
while (curn--) {
auto t = q.front();
q.pop();
if (t == b) { //在弹出时判断是否相等
cout << res << endl;
return 0;
}
//t可以走到哪里
if (t + 1 < N && t + 1 >= 0 && !st[t+1]) {
q.push(t + 1);
st[t+1] = true;
}
if (t - 1 < N && t - 1 >= 0 && !st[t-1]) {
q.push(t - 1);
st[t-1] = true;
}
if (2 * t < N && 2 * t >= 0 && !st[2 * t]) {
q.push(2 * t);
st[2 * t] = true;
}
}
res++;
}
return 0;
}
方法2:动态规划
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100010;
int a, b;
int f[N]; //f[i]:从a到i的最小步数
int main() {
cin >> a >> b;
//初始化
memset(f, 0x3f, sizeof f);
f[a] = 0;
//<=a状态转移
for (int i = a; i >= 0; --i) {
f[i] = min(f[i], f[i+1] + 1);
}
//>a状态转移
//(1)按照以下路径过来i-1 -> i
//(2)i是偶数,按照以下路径过来i/2 -> i
//(3)i是奇数,按照以下路径过来(i-1)/2 -> i-1 -> i
//(4)i是奇数,按照以下路径过来(i+1)/2 -> i+1 -> i
for (int i = a + 1; i <= b; ++i) {
f[i] = min(f[i], f[i-1] + 1);
if (i % 2 == 0) {
f[i] = min(f[i], f[i/2] + 1);
} else {
f[i] = min(f[i], f[(i-1)/2] + 2);
f[i] = min(f[i], f[(i+1)/2] + 2);
}
}
cout << f[b] << endl;
return 0;
}