🧠 算法学习日记 | 今天我用「模拟」解了三道题,原来"暴力"也能很聪明!
大家好,我是你们的算法学习搭子 👋
今天继续我的算法入门之旅,重点练习了**模拟(Simulation)**这一看似简单却极其实用的方法。
很多人觉得"模拟=暴力",但其实,模拟是把现实过程一步步还原到代码中。它不需要复杂的数学推导,只需要我们能清晰地理解问题逻辑,然后用循环和条件语句"一步一步走"。
今天我完整做了三道题,每一道都坚持用最朴素的模拟方法解决。下面我把题目原文 和我的原始代码原封不动贴出来,不做任何删减或美化,只为真实记录学习过程。
🔹 题目一:扫雷
题目描述
在一个 n 行 m 列的方格图上有一些位置有地雷,另外一些位置为空。
请为每个空位置标一个整数,表示周围八个相邻的方格中有多少个地雷。
输入描述输入的第一行包含两个整数 n, m 。
第 2 行到第 n+1 行每行包含 m 个整数,相邻整数之间用一个空格分隔。如果对应的整数为 0,表示这一格没有地雷。如果对应的整数为 1,表示这一格有地雷。
其中, 1 \\leq n, m \\leq 100 。
输出描述输出 n 行,每行 m 个整数,相邻整数之间用空格分隔。
对于没有地雷的方格,输出这格周围的地雷数量。对于有地雷的方格,输出 9。
输入输出样例
输入
3 4
0 1 0 0
1 0 1 0
0 0 1 0
输出
2 9 2 1
9 4 9 2
1 3 9 2
运行限制
- 最大运行时间:1s
- 最大运行内存:128M
✅ 我的代码
cpp
#include <iostream>
using namespace std;
const int N=150;
int mp[N][N];
int ans[N][N];
int main()
{
int m,n;
cin>>m>>n;
for(int i=1;i<=m;++i){
for(int j=1;j<=n;++j){
cin>>mp[i][j];
}
}
for(int i=1;i<=m;++i){
for(int j=1;j<=n;++j){
if(mp[i][j]){
ans[i][j]=9;
continue;
}
for(int _i=max(1,i-1);_i<=min(m,i+1);++_i){
for(int _j=max(1,j-1);_j<=min(n,j+1);++_j){
if(mp[_i][_j]){
ans[i][j]++;
}
}
}
}
}
for(int i=1;i<=m;++i){
for(int j=1;j<=n;++j){
cout<<ans[i][j]<<" ";
}
cout<<endl;
}
return 0;
}
🔹 题目二:灌溉
题目描述
小蓝负责花园的灌溉工作。
花园可以看成一个 n 行 m 列的方格图形。中间有一部分位置上安装有出水管。
小蓝可以控制一个按钮同时打开所有的出水管,打开时,有出水管的位置可以被认为已经灌溉好。
每经过一分钟,水就会向四面扩展一个方格,被扩展到的方格可以被认为已经灌溉好。即如果前一分钟某一个方格被灌溉好,则下一分钟它上下左右的四个方格也被灌溉好。
给定花园水管的位置,请问 k 分钟后,有多少个方格被灌溉好?
输入描述输入的第一行包含两个整数 n, m 。
第二行包含一个整数 t ,表示出水管的数量。
接下来 t 行描述出水管的位置,其中第 i 行包含两个数 r, c ,表示第 r 行第 c 列有一个排水管。
接下来一行包含一个整数 k 。其中, 。 其中, 。其中, 1 \\leq n, m \\leq 100, 1 \\leq t \\leq 10, 1 \\leq k \\leq 100 。
输出描述输出一个整数,表示答案。
输入输出样例
输入
3 6
2
2 2
3 4
1
输出
9
✅ 我的代码
cpp
#include <iostream>
using namespace std;
const int N = 105;
int a[N][N];
int main() {
int m, n;
cin >> m >> n;
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
a[i][j] = 0;
}
}
int t;
cin >> t;
for (int i = 1; i <= t; ++i) {
int x, y;
cin >> x >> y;
a[x][y] = 1;
}
int k;
cin >> k;
for (int minute = 0; minute < k; ++minute) {
int temp[N][N] = {0};
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
temp[i][j] = a[i][j];
}
}
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
if (a[i][j]) {
if (i - 1 >= 1) temp[i - 1][j] = 1;
if (j - 1 >= 1) temp[i][j - 1] = 1;
if (i + 1 <= m) temp[i + 1][j] = 1;
if (j + 1 <= n) temp[i][j + 1] = 1;
}
}
}
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
a[i][j] = temp[i][j];
}
}
}
int count = 0;
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
count += a[i][j];
}
}
cout << count;
return 0;
}
🔹 题目三:回文日期
题目描述
2020 年春节期间,有一个特殊的日期引起了大家的注意:2020 年 2 月 2 日。因为如果将这个日期按 "yyyymmdd" 的格式写成一个 8 位数是 20200202,恰好是一个回文数。我们称这样的日期是回文日期。
有人表示 20200202 是 "千年一遇" 的特殊日子。对此小明很不认同,因为不到 2 年之后就是下一个回文日期:20211202 即 2021 年 12 月 2 日。
也有人表示 20200202 并不仅仅是一个回文日期,还是一个 ABABBABA 型的回文日期。对此小明也不认同,因为大约 100 年后就能遇到下一个 ABABBABA 型的回文日期:21211212 即 2121 年 12 月 12 日。算不上 "千年一遇",顶多算 "千年两遇"。
给定一个 8 位数的日期,请你计算该日期之后下一个回文日期和下一个 ABABBABA 型的回文日期各是哪一天。
输入描述
输入包含一个八位整数 N ,表示日期。对于所有评测用例, ,表示日期。 对于所有评测用例, ,表示日期。对于所有评测用例, 10000101 \\leq N \\leq 89991231 ,保证 N 是一个合法日期的 8 位数表示。
输出描述输出两行,每行 1 个八位数。第一行表示下一个回文日期,第二行表示下一个 ABABBABA 型的回文日期。
输入输出样例
输入
20200202
输出
20211202
21211212
运行限制
- 最大运行时间:1s
- 最大运行内存:256M
✅ 我的代码
cpp
#include <iostream>
using namespace std;
bool isleap(int y) { //判断闰年
return (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0); //闰年可以被4整除但不能被100整除,或能被400整除。
}
bool check(int year, int month, int day) { //判断日期是否合法
if (month > 12 || month == 0) return false;
if (day > 31) return false;
if (month == 2) {
if (isleap(year) && day > 29)
return false;
if (!isleap(year) && day > 28)
return false;
}
if (month == 4 || month == 6 || month == 9 || month == 11)
if (day > 30) return false;
return true;
}
int main()
{
int n, i;
cin >> n;
int a, b, c, d, e, f, g, h;
int year, month, day;
bool flag = false;
for (i = n + 1; i <= 99999999; ++i) {
year = i / 10000;
month = i % 10000 / 100;
day = i % 100;
a = year / 1000;
b = year / 100 % 10;
c = year % 100 / 10;
d = year % 10;
e = month / 10;
f = month % 10;
g = day / 10;
h = day % 10;
if (a == h && c == f && b == g && e == d && flag == false) { //该部分目的是输出第一个回文日期,flag作为标记。
//当找到第一个回文日期之后,将flag变为true。这样下一次碰到普通回文日期时就不会输出。
if (check(year, month, day)) {
cout << i << endl;
flag = true;
}
}
if (a == h && c == f && b == g && e == d && a == c && b == e) { //输出ABABBABA型的回文日期
if (check(year, month, day)) {
cout << i << endl;
break;
}
}
}
return 0;
}
🌟 我的思考
这三道题,虽然看起来完全不同,但都用了模拟的思想:
- 扫雷:模拟每个格子的邻居统计过程,遍历所有位置,对每个非雷格子计算周围雷数。
- 灌溉 :模拟水从初始点开始每分钟向四周扩散的过程,用
temp数组避免当前状态干扰。 - 回文日期:模拟从当前日期往后逐天枚举,直到找到满足条件的回文日。
你会发现:
模拟的本质不是"暴力",而是"还原过程"。
只要你能把问题拆解成"每一步做什么",就能写出正确的代码。哪怕数据范围大一点,只要逻辑正确,也能通过。
而且,很多高级算法(如 BFS、DFS、动态规划)都是建立在"模拟"的基础上的。先学会"一步一步走",才能学会"跳着走"。
✅ 总结
- 模拟 ≠ 暴力,它是过程还原
- 要清晰理解问题的每一步操作
- 可以用辅助数组避免状态污染(如灌溉题)
- 枚举 + 判断 + 循环 = 模拟的核心三件套
- 先求正确,再求高效
如果你也在刷算法题,不妨试试今天这三道题,用最直白的模拟方法做一遍。
有时候,慢一点,反而更快。
欢迎在评论区贴出你的解法,我们一起交流进步!👇