蓝桥杯基础--模拟

目录

[1. 模拟算法简介](#1. 模拟算法简介)

[2. 例题](#2. 例题)

2.1扫雷

2.2灌溉

2.3回文日期


在蓝桥杯以及各种编程竞赛中,有一种题型几乎每次都会出现,它不需要你掌握多么高深复杂的数据结构(比如线段树、图论),也不需要你推导晦涩的数学公式,它考验的纯粹是你的"代码实现能力"和"细心程度"。这种题型,我们通常称之为"模拟"。

今天我们就来详细聊聊模拟算法,并结合三道经典的蓝桥杯真题,带你掌握应对模拟题的技巧。

1. 模拟算法简介

模拟(Simulation),顾名思义,就是"题目怎么说,你就怎么做"。

模拟题的核心特征是:题目会给你一套非常明确的规则、流程或者操作步骤,你需要做的就是用代码把这个过程一步一步地"翻译"出来。计算机的优势在于计算速度极快且不会出错,我们要利用这一优势,让计算机去重现题目中描述的物理过程或逻辑流程。

虽然模拟题在思维难度上通常不大,但往往是比赛中最容易让人"翻车"的题型。因为模拟题通常具有以下特点:

  • 细节繁多:可能会有各种边界条件和特殊情况需要处理。

  • 代码量大:流程一旦复杂,嵌套的 if-else 和循环就会非常多,容易写错或者漏掉条件。

  • 状态依赖:很多模拟过程是按时间推进的,当前状态往往依赖于上一个状态,状态更新时的顺序极其重要。

做好模拟题的秘诀只有两个字:细心。在动手敲代码之前,一定要先在草稿纸上把流程图或者状态变化画清楚,理清变量的更新顺序,然后再开始编写。

2. 例题

下面我们通过三道不同类型的蓝桥杯真题,来看看模拟题在实战中是如何考察的。

2.1扫雷

https://www.lanqiao.cn/problems/549/learning/?page=1&first_category_id=1&problem_id=549

思路解析: 这道题是经典的二维数组模拟。题目要求我们根据给定的地雷分布,计算出每个非地雷格子周围一圈(也就是常见的九宫格区域)有多少颗地雷。 对于这种题,最直白的方法就是遍历整个二维矩阵。当我们遇到一个格子时: 如果是地雷(值为1),我们就按题目要求(或者为了方便标记)将结果标记为9。 如果不是地雷,我们就再写两层小循环,遍历它周围3乘3的区域,统计地雷的个数。 这段代码中有一个非常精妙的防越界技巧:在内层循环中使用了 max 和 min 函数。比如 _i 的范围是 max(1, i - 1)min(n, i + 1),这样就算当前格子在矩阵的最边缘,也不会发生数组越界报错。

复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int mp[N][N], ans[N][N];

int main()
{
  int n, m; cin >> n >> m;
  for(int i = 1; i <= n; i++)
  {
    for(int j = 1; j <= m; j++)
    {
      cin >> mp[i][j];
    }
  }

  for(int i = 1; i <= n; i++)
  {
    for(int j = 1; j <= m; j++)
    {
      if(mp[i][j] == 1)
      {
        ans[i][j] = 9;
        continue;
      }

      for(int _i = max(1, i - 1); _i <= min(n, i + 1); _i++)
      {
        for(int _j = max(1, j - 1); _j <= min(m, j+ 1); _j++)
        {
          if(mp[_i][_j]) ans[i][j]++;
        }
      }
    }
  }
  for(int i = 1; i <= n; i++)
  {
    for(int j = 1; j <= m; j++)
    {
      cout << ans[i][j] << " ";
    }
    cout << '\n';
  }

  return 0;
}

2.2灌溉

https://www.lanqiao.cn/problems/551/learning/?page=1&first_category_id=1&problem_id=551

思路解析: 这是一道非常经典的状态推演模拟题。水管每分钟都会向上下左右四个方向蔓延。我们需要模拟这个蔓延的过程,持续 k 分钟。 这里的核心考点是:状态的隔离 。如果我们在原来的矩阵上直接修改(把没水的改成有水),那么在同一分钟内,刚刚被水蔓延到的新格子,可能紧接着又会向外蔓延,这就导致了一分钟蔓延了多步的错误。 为了解决这个问题,代码中使用了两个矩阵:mp 用来保存当前分钟的初始状态,ans 用来记录蔓延后的新状态。每次遍历 mp 找到有水的地方,将周围的格子在 ans 中标记为有水。一分钟结束后,再把 ans 的状态同步回 mp,准备下一分钟的模拟。

参考代码:

复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
bool mp[N][N], ans[N][N];

int main()
{
  int n, m, t;
  cin >> n >> m;
  cin >> t;

  for(int i = 1; i <= t; i++)
  {
    int x, y;
    cin >> x >> y;
    mp[x][y] = 1;
    ans[x][y] = 1;
  }

  int k; cin >> k;
  while(k--)
  {
    for(int i = 1; i <= n; i++)
    {
      for(int j = 1; j <= m; j++)
      {
        if(mp[i][j])
          ans[i][j] = ans[i - 1][j] = ans[i + 1][j] = ans[i][j - 1] = ans[i][j + 1] = 1;
      }
    }

    for(int i = 1; i <= n; i++)
    {
      for(int j = 1; j <= m; j++)
      {
        mp[i][j] = ans[i][j];
      }
    }
  }
  int ret = 0;
  for(int i = 1; i <= n; i++)
  {
    for(int j = 1; j <= m; j++)
    {
      if(mp[i][j]) ret++;
    }
  }
    cout << ret << "\n";
  return 0;
}

2.3回文日期

https://www.lanqiao.cn/problems/498/learning/?page=1&first_category_id=1&problem_id=498

思路解析: 这是一道非常经典的日期模拟题。题目要求找出一个给定日期之后的下一个回文日期,以及下一个满足 ABABBABA 格式的特殊回文日期。 如果一天一天地往后加,还需要处理复杂的进位和闰年判断,既容易超时又极其容易写错。 换一种更聪明的模拟思路:既然是回文日期,前四位(年份)直接决定了后四位(月日)。所以我们大可不必一天一天枚举,直接从给定的年份开始,向下枚举年份。 对于每一个年份,我们通过反转拼凑出对应的8位回文日期,然后判断这个日期:

  1. 是否比输入的日期 n 大。

  2. 是否是一个合法的真实日期(利用打表的 days 数组和闰年判断函数 isleapYear)。 如果是合法日期,我们再通过简单的数学运算分离出A和B,判断是否满足 ABABBABA 格式即可。这样不仅代码逻辑清晰,而且运行速度极快。

参考代码:

复制代码
#include <bits/stdc++.h>
using namespace std;

int days[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

bool isleapYear(int year)
{
  return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}

bool checkDate(int year, int month, int day)
{
  if(month < 1 || month > 12 || day < 1) return false;
  if(month != 2) return day <= days[month];
  if(isleapYear(year)) return day <= 29;
  return day <= 28; 
}

int main()
{
  int n; 
  cin >> n;
  bool found1 = false, found2 = false;

  int startYear = n / 10000;
  for(int i = startYear; i <= 9999; i++)
  {
    int date = i * 10000 + (i % 10) * 1000 + (i / 10 % 10) * 100 + (i / 100 % 10) * 10 + (i / 1000);
    if(date <= n) continue;

    int month = (date / 100) % 100;
    int day = date % 100;

    if(checkDate(i, month, day))
    {
      if(!found1) 
      {
        cout << date << '\n';
        found1 = true;
      }
      int A1 = i /1000;
      int B1 = (i / 100) % 10;
      int A2 = (i / 10) % 10;
      int B2 = i % 10;

      if(!found2 && A1 == A2 && B1 == B2 && A1 != B1)
      {
        cout << date << '\n';
        found2 = true;
      }
    }
    if(found1 && found2) break;
  }
  return 0;
}

本章完。

相关推荐
m0_488633323 小时前
C语言中结构体指针如何用 -> 取子数据及链表应用示例
c语言·数据结构·结构体指针·链表应用·指针操作
Sakinol#3 小时前
Leetcode Hot 100 ——动态规划part02
算法·leetcode·动态规划
sqyno1sky3 小时前
零成本抽象在C++中的应用
开发语言·c++·算法
cm6543203 小时前
C++中的职责链模式
开发语言·c++·算法
平生幻3 小时前
【数据结构】-复杂度
java·开发语言·数据结构
小菜鸡桃蛋狗3 小时前
C++——类和对象(中)
开发语言·c++
MORE_773 小时前
leecode-灌溉花园-贪心算法and动态规划
算法·贪心算法·动态规划
feng_you_ying_li3 小时前
c++之二叉搜索树的实现
c++
晚枫歌F3 小时前
线程池的理解使用以及代码详解
数据结构