📚 流感传染
🚀 题目浏览
【题目名称】流感传染
【题目描述】
有一批易感人群住在网格状的宿舍区内,宿舍区为 n ∗ n n*n n∗n 的矩阵,每个格点为一个房间,房间里可能住人,也可能空着。
在第一天,有些房间里的人得了流感,以后每天,得流感的人会使其邻居传染上流感(已经得病的不变),空房间不会传染。
请输出第 m m m 天得流感的人数。
【输入】
第一行一个数字 n n n, n n n 不超过 100 100 100,表示有 n ∗ n n*n n∗n 的宿舍房间。
接下来的 n n n 行,每行 n n n 个字符,’.’ 表示第一天该房间住着健康的人,’#’ 表示该房间空着,’@’ 表示第一天该房间住着得流感的人。
接下来的一行是一个整数 m m m, m m m 不超过 100 100 100。
【输出】
输出第 m m m 天,得流感的人数。
【输入样例】
5
....#
.#.@.
.#@..
#....
.....
4
【输出样例】
16
【原题链接】
http://ybt.ssoier.cn:8088/problem_show.php?pid=1191
☘️ 题解分析
推荐前往摊主的个人博客查看该题解,享受更好的阅读体验(且由于时间精力有限,摊主发现文章错误后一般只在个人博客中修改,这里的文章版本可能不是最新的)
主站点:信息奥赛题解|流感传染
备用站点:信息奥赛题解|流感传染
在博主看来,本题对于 算法初学者 主要有以下几个 难点:
- 如何读入字符矩阵 ?
- 如何模拟感染过程 ?
对于第一个难点,博主采用的方式是 「定义一个 二维的字符数组、使用 cin 读入数据」(需要开启 cin 读入优化,减少读入数据的时间)。
const int N = 105;
char s[N][N]; // 定义一个二维的字符数组
int main(){
//cin 读入优化
ios::sync_with_stdio(false);
cin.tie(0);
//读入矩阵
cin >> n;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
cin >> s[i][j];
}
}
return 0;
}
🍉 PS:使用 scanf
读入时,需要考虑 换行符的读入,而 cin
则不用,这是 scanf
的缺点;但 scanf
的读入速度比没有开 cin 读入优化的 cin
快很多,这是 scanf
的优点。
cin
在开了 cin 读入优化后,速度基本上能达到 scanf
的水平,此题完全够用了。
对于第二个难点,博主采用了 遍历模拟 的方法,每天都遍历一次二维矩阵,模拟感染过程。(本题应该也可以使用 广度优先搜索 BFS 的方法,等博主研究完 「搜索」专题 后再来补充)🧑🏻💻
模拟过程也非常简单,只需在遍历时,遇到 ‘@’ 的房间,就把四周非空的房间置为 ‘@’
❗️❗️❗️但是这种方式非常容易出现 一个误区,就是 「每次矩阵还没有遍历完,就急着把 当前房间感染的新房间置为 ‘@’ 」
这样会出现下面这种情况:
- 第一天,初始情况
@..
#..
- 第二天,遍历到第 1 个房间时
@@.
#..
- 第二天,遍历到第 2 个房间时,由于当前房间被置为了 ‘@’,所以其周围的房间也被置为 ‘@’
@@@
#@.
- 第二天,遍历到第 3 个房间时,由于当前房间被置为了 ‘@’,所以其周围的房间也被置为 ‘@’
@@@
#@@
-
… 省略剩下的遍历过程
-
第二天的最终结果
@@@
#@@
这样的得到的结果显然是不对的 ❌
因为实际上第 2 天,仅有 2 号房间被感染,3 号房间需要等到第 3 天才会被感染。✅
- 第二天实际结果
@@.
#..
所以,我们需要借助一个标记数组 b,存储当天被感染的房间,而不是在遍历时直接修改原矩阵的值。
等当天的 遍历结束后,再根据标记数组 b 来修改原矩阵的值,这样就不会出现上面这种情况了。
从代码的角度,是这样的区别:
//模拟感染过程,并标记
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if (s[i][j] == '@') {
// 错误的直接修改法
//if (s[i - 1][j] != '#') s[i - 1][j] = '@';
//if (s[i + 1][j] != '#') s[i + 1][j] = '@';
//if (s[i][j - 1] != '#') s[i][j - 1] = '@';
//if (s[i][j + 1] != '#') s[i][j + 1] = '@';
// 正确的标记法
if (s[i - 1][j] != '#') b[i - 1][j] = 1;
if (s[i + 1][j] != '#') b[i + 1][j] = 1;
if (s[i][j - 1] != '#') b[i][j - 1] = 1;
if (s[i][j + 1] != '#') b[i][j + 1] = 1;
}
}
}
//将带有标记的房间感染
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if (b[i][j] == 1) {
s[i][j] = '@';
}
}
}
🧑🏻💻 C++ 代码
#include<bits/stdc++.h>
using namespace std;
const int N = 105;
char s[N][N]; // 存储房间状态
int b[N][N]; //用于感染的标记数组
int n, m, cnt;
// 每天更新矩阵
void update();
// 统计并输出感染人数
void print();
int main() {
ios::sync_with_stdio(false); // cin读入优化
cin.tie(0);
//读入矩阵
cin >> n;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
cin >> s[i][j];
}
}
// 统计第m天,需要更新m-1次
cin >> m;
while (--m) {
//更新矩阵
update();
}
// 统计感染总人数
print();
return 0;
}
void update() {
//初始化感染标记数组b
memset(b, 0, sizeof(b));
//模拟感染过程,并标记
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
// 向四个方向进行感染,标记位置
// 注意不能马上把相应房间改为 @,否则会出现一条线上的房间一天就感染完等情况
if (s[i][j] == '@') {
if (s[i - 1][j] != '#') b[i - 1][j] = 1;
if (s[i + 1][j] != '#') b[i + 1][j] = 1;
if (s[i][j - 1] != '#') b[i][j - 1] = 1;
if (s[i][j + 1] != '#') b[i][j + 1] = 1;
}
}
}
//将带有标记的房间感染
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if (b[i][j] == 1) {
s[i][j] = '@';
}
}
}
}
void print() {
cnt = 0;
//统计感染人数
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if (s[i][j] == '@')
cnt++;
}
}
cout << cnt << endl;
}
✍️ 写在最后
如果各位小伙伴觉得博主的题解写的不错,可以给本文 点个赞 👍
这样可以让 更加优质的文章 有 更大的概率 被推送到 搜索界面的榜首,为未来的小伙伴们节约更多搜索、阅读的成本。 🚀
同时,你的支持 也是我 不断创作 的动力。☘️
有想要看更多题解报告的小伙伴,也可以关注我的专栏「信息奥赛题解」。
我们下期再见。👋