备战蓝桥杯国赛【day1】

写在前面

今天完成了蓝桥杯的五道真题,从螺旋矩阵的边界控制,到图像模糊的卷积模拟,再到日期回文的数学规律,冰雹数的记忆化剪枝,最后是多源BFS的扩散模型。

这五道题恰好构成了一条从暴力到优化的完整学习路径。记录如下,既是复盘,也希望对同样备战蓝桥杯的你有所启发。


第一题:螺旋矩阵

题目大意

给定 n × m n \times m n×m 的矩阵,按顺时针螺旋方式填入 1 1 1 到 n × m n \times m n×m,求第 r r r 行第 c c c 列的数字。

核心思路:边界碰撞法

螺旋填充的本质是方向优先级 :右 → 下 → 左 → 上,循环往复。关键在于何时转向 ------当前进方向的下一个格子越界或已填充时,立即转向。

代码实现

python 复制代码
n, m = map(int, input().split())
r, c = map(int, input().split())

ans = [[0] * m for _ in range(n)]
x, y = 0, 0
value = 1
ans[x][y] = value

while value < n * m:
    # 右 → 下 → 左 → 上,顺时针螺旋
    while y + 1 < m and ans[x][y + 1] == 0:
        y += 1
        value += 1
        ans[x][y] = value

    while x + 1 < n and ans[x + 1][y] == 0:
        x += 1
        value += 1
        ans[x][y] = value

    while y - 1 >= 0 and ans[x][y - 1] == 0:
        y -= 1
        value += 1
        ans[x][y] = value

    while x - 1 >= 0 and ans[x - 1][y] == 0:
        x -= 1
        value += 1
        ans[x][y] = value

print(ans[r - 1][c - 1])

深度思考:为什么这样写最清晰?

很多初学者喜欢用 dx, dy 方向数组配合 dir 变量转向,但这道题四个方向的判断条件不同 (右看列边界,下看行边界),用四个独立的 while 反而语义最清晰

关键细节ans[x][y+1] == 0 这个条件同时承担了边界检查访问检查两个职责,是螺旋算法的精髓。

时间复杂度

O ( n m ) O(nm) O(nm),每个格子恰好访问一次。


第二题:图像模糊

题目大意

对 n × m n \times m n×m 的灰度图像做均值滤波:每个像素替换为其 3 × 3 3 \times 3 3×3 邻域(含边界不足9个的情况)的平均值(下取整)。

核心思路:卷积核模拟

这是计算机视觉中最基础的均值模糊 (Box Blur)。对于每个像素 ( i , j ) (i,j) (i,j),需要统计其 3 × 3 3 \times 3 3×3 邻域内的有效像素和个数。

代码实现

python 复制代码
n, m = map(int, input().split())
img = [list(map(int, input().split())) for _ in range(n)]

# 9个方向的偏移量(包含自身)
dirs = [(-1, -1), (-1, 0), (-1, 1),
        (0, -1),  (0, 0),  (0, 1),
        (1, -1),  (1, 0),  (1, 1)]

ans = [[0] * m for _ in range(n)]

for i in range(n):
    for j in range(m):
        total, cnt = 0, 0
        for dx, dy in dirs:
            x, y = i + dx, j + dy
            if 0 <= x < n and 0 <= y < m:  # 边界检查
                total += img[x][y]
                cnt += 1
        ans[i][j] = total // cnt  # 下取整

for row in ans:
    print(*row)

深度思考:边界处理的优雅写法

注意 0 <= x < n 这种链式比较 是 Python 的语法,比 x >= 0 and x < n 更简洁。

为什么用 // 而不是 int()
// 是向下取整除法,对于正数等同于 int(a/b),但速度更快。

扩展:高斯模糊怎么做?

如果 weights 不是均匀的,而是呈高斯分布(中心权重高,边缘权重低),则:

python 复制代码
weights = [[1, 2, 1],
           [2, 4, 2],
           [1, 2, 1]]  # 高斯核
total = sum(weights[x][y] * img[i+dx][j+dy] for ...)
ans[i][j] = total // sum(sum(row) for row in weights)

第三题:回文日期

题目大意

给定日期 N N N(8位整数,如 20200202),求:

  1. 下一个普通回文日期(如 20211202
  2. 下一个 ABABBABA 型回文日期(如 21211212

核心思路:日期迭代 + 字符串回文判断

代码实现

python 复制代码
from datetime import datetime, timedelta

n = int(input())
start = datetime.strptime(str(n), '%Y%m%d').date()
delta = timedelta(days=1)

found_plain = False  # 是否已找到普通回文

while True:
    start += delta
    date_str = start.strftime('%Y%m%d')
    
    # 普通回文:正读反读相同
    if not found_plain and date_str == date_str[::-1]:
        print(date_str)
        found_plain = True
    
    # ABABBABA 型:位置 0,2,4,6 相同,位置 1,3,5,7 相同
    # 即 date_str[0]==date_str[2]==date_str[4]==date_str[6]
    # 且 date_str[1]==date_str[3]==date_str[5]==date_str[7]
    if (date_str[0] == date_str[2] == date_str[4] == date_str[6] and
        date_str[1] == date_str[3] == date_str[5] == date_str[7]):
        print(date_str)
        break

深度思考:为什么不用数学法?

理论上可以直接构造回文日期(年决定日,月需合法),但日期合法性判断极其繁琐 (闰年、大小月、二月天数)。用 datetime 迭代虽然看似暴力,但:

  1. 代码极短,不易出错
  2. 日期跨度有限(下一个回文日期不会太远)
  3. Python 的日期库足够快

ABABBABA 的本质YYYYMMDD 中,Y[0]==Y[2]==M[0]==D[0]Y[1]==Y[3]==M[1]==D[1],即年份前两位决定一切。

优化方向:数学构造

若数据范围极大(如求第 10 6 10^6 106 个回文日期),则需数学法:

  • 枚举年份 Y Y Y(4位)
  • 构造回文:Y * 10000 + reverse(Y)
  • 检查月日合法性

第四题:冰雹数(重点!)

题目大意

对任意正整数 N N N,如果是偶数则 N / 2 N/2 N/2,如果是奇数则 3 N + 1 3N+1 3N+1,最终必到 1 1 1。求 2 2 2 到 N N N 中,变换过程中出现的最大值

初版:暴力模拟(TLE风险)

python 复制代码
n = int(input())
max_num = n

for start in range(2, n + 1):
    x = start
    while x != 1:
        if x % 2 == 0:
            x //= 2
        else:
            x = x * 3 + 1
        max_num = max(max_num, x)

print(max_num)

问题 : N < 10 6 N < 10^6 N<106,但冰雹序列可能极长(如 N = 77031 N=77031 N=77031 时序列长度达 350 350 350),总复杂度接近 O ( N × L ) O(N \times L) O(N×L),可能超时。

优化版:记忆化剪枝

关键洞察 :若计算 N = 10 N=10 N=10 时,序列经过 5 5 5,而 N = 5 N=5 N=5 的结果已计算过,则可直接复用。

但本题求的是过程中的最大值而非序列长度,剪枝策略需调整:

python 复制代码
n = int(input())
max_num = n

for start in range(2, n + 1):
    x = start
    while x != 1:
        if x % 2 == 0:
            x //= 2
        else:
            x = x * 3 + 1
            max_num = max(max_num, x)  # 奇数变换后可能更大
        
        # 剪枝:若 x 已小于 start,且 start 之前已遍历过
        # 则 x 的最大值不可能超过当前 max_num
        if x < start:
            break

print(max_num)

深度思考:剪枝的正确性证明

核心观察 :对于 x < s t a r t x < start x<start,我们在遍历 s t a r t start start 时, x x x 作为更小的起始值已经被处理过 。其产生的最大值已经被纳入 max_num 的考虑范围。

因此,一旦当前序列降落到 x < s t a r t x < start x<start,就可以安全终止,因为:

  1. x x x 的后续序列是确定的
  2. x x x 序列中的最大值已在之前计算时考虑
  3. 无需重复计算

这本质上是一种自底向上的动态规划思想

更优解:全局记忆化

python 复制代码
from functools import lru_cache

@lru_cache(maxsize=None)
def hailstone_max(x):
    if x == 1:
        return 1
    if x % 2 == 0:
        nxt = x // 2
    else:
        nxt = x * 3 + 1
    return max(x, hailstone_max(nxt))

n = int(input())
print(max(hailstone_max(i) for i in range(1, n + 1)))

第五题:长草(重点!多源BFS)

题目大意

n × m n \times m n×m 的草地,初始有些格子有草。每月草向上下左右扩散一格,求 k k k 月后的状态。

初版:暴力模拟(过掉80%)

python 复制代码
n, m = map(int, input().split())
dirs = [(-1, 0), (1, 0), (0, -1), (0, 1)]

grid = [list(input()) for _ in range(n)]
k = int(input())

for _ in range(k):
    new_grid = [row.copy() for row in grid]
    for i in range(n):
        for j in range(m):
            if grid[i][j] == 'g':
                for dx, dy in dirs:
                    x, y = i + dx, j + dy
                    if 0 <= x < n and 0 <= y < m:
                        new_grid[x][y] = 'g'
    grid = new_grid

for row in grid:
    print(''.join(row))

问题 : k ≤ 1000 k \leq 1000 k≤1000,每月全图扫描 O ( n m ) O(nm) O(nm),总复杂度 O ( k n m ) O(knm) O(knm),对于 1000 × 1000 1000 \times 1000 1000×1000 的极限数据会超时。

优化版:多源BFS

核心洞察 :草的扩散是同时的、均匀的,这完全符合BFS的层级遍历特性。

  • 初始所有草的位置作为多源起点
  • 每层BFS恰好对应一个月的扩散
  • 每个空地只被访问一次,总复杂度 O ( n m ) O(nm) O(nm)
python 复制代码
from collections import deque

n, m = map(int, input().split())
dirs = [(-1, 0), (1, 0), (0, -1), (0, 1)]

grid = [list(input()) for _ in range(n)]
k = int(input())

# 多源BFS初始化:所有草同时入队
q = deque()
for i in range(n):
    for j in range(m):
        if grid[i][j] == 'g':
            q.append((i, j))

def bfs_layer():
    """扩散一层(一个月)"""
    size = len(q)
    for _ in range(size):
        x, y = q.popleft()
        for dx, dy in dirs:
            nx, ny = x + dx, y + dy
            if 0 <= nx < n and 0 <= ny < m and grid[nx][ny] == '.':
                grid[nx][ny] = 'g'
                q.append((nx, ny))

# 执行 k 个月
for _ in range(k):
    bfs_layer()

for row in grid:
    print(''.join(row))

深度思考:为什么BFS更优?

维度 暴力模拟 多源BFS
每月操作 全图扫描 O ( n m ) O(nm) O(nm) 仅处理边界草 O ( perimeter ) O(\text{perimeter}) O(perimeter)
重复访问 已长草的格子反复判断 每个格子仅入队一次
总复杂度 O ( k n m ) O(knm) O(knm) O ( n m ) O(nm) O(nm)
空间复杂度 O ( n m ) O(nm) O(nm) 复制数组 O ( n m ) O(nm) O(nm) 队列

BFS的本质 :将"时间"维度转化为"图上的距离"。每个空地被草覆盖的月份 = 它到最近初始草地的曼哈顿距离

终极优化:直接计算距离

若只问最终状态,甚至可以不用模拟:

python 复制代码
from collections import deque

n, m = map(int, input().split())
grid = [list(input()) for _ in range(n)]
k = int(input())

# 计算每个空地到最近草地的距离
dist = [[-1] * m for _ in range(n)]
q = deque()

for i in range(n):
    for j in range(m):
        if grid[i][j] == 'g':
            q.append((i, j))
            dist[i][j] = 0

dirs = [(-1, 0), (1, 0), (0, -1), (0, 1)]

while q:
    x, y = q.popleft()
    for dx, dy in dirs:
        nx, ny = x + dx, y + dy
        if 0 <= nx < n and 0 <= ny < m and dist[nx][ny] == -1:
            dist[nx][ny] = dist[x][y] + 1
            q.append((nx, ny))

# 距离 <= k 的格子都长草
for i in range(n):
    for j in range(m):
        if grid[i][j] == 'g' or dist[i][j] <= k:
            print('g', end='')
        else:
            print('.', end='')
    print()

这揭示了问题的本质 :多源BFS一次求出所有最短距离,之后 O ( 1 ) O(1) O(1) 判断每个格子。


五题复盘:算法思想的递进

题号 题目 核心算法 优化关键词
1 螺旋矩阵 模拟 边界碰撞、访问标记
2 图像模糊 卷积模拟 方向数组、链式比较
3 回文日期 枚举 日期库、字符串回文
4 冰雹数 模拟 + 剪枝 记忆化、自底向上
5 长草 多源BFS 层级遍历、距离转化

给蓝桥杯选手的建议

1. 先暴力,再优化

竞赛中先写出暴力解法拿部分分,再思考优化。第5题的80分暴力在考场上完全可先提交。

2. 识别问题结构

  • 看到"同时扩散" → 想BFS
  • 看到"重复子问题" → 想DP/记忆化
  • 看到"网格遍历" → 想方向数组 + DFS/BFS

3. Python的竞争力

Python在蓝桥杯完全够用,关键是:

  • 熟练使用 deque 做BFS
  • 熟练使用 datetime 处理日期
  • 熟练使用列表推导式和切片

4. 边界意识

蓝桥杯很多分丢在边界:

  • 螺旋矩阵的 0 起始 vs 1 起始
  • 图像模糊的边界不足9个像素
  • 日期迭代的 while True 终止条件

结语

五道题,从螺旋到回文,从冰雹到长草,看似各异,实则相通:识别结构,选择工具,优雅实现

算法竞赛的魅力不在于写出最复杂的代码,而在于用最简洁的代码,表达最清晰的思路

"代码是写给人看的,顺便给机器执行。"

------ Harold Abelson

愿你在蓝桥杯的赛场上,也能写出这样优雅的代码。

如需调整某个题目的分析深度,或补充更多图示说明,请告诉我!

相关推荐
szccyw02 小时前
如何防止 Laravel 中因动态列名导致的 SQL 注入风险
jvm·数据库·python
zhangchaoxies2 小时前
团队版Navicat专属功能:如何共享数据库架构ER模型_核心机制解析
jvm·数据库·python
凤头百灵鸟2 小时前
Python语法进阶篇 --- 单例模式、魔法方法
javascript·python·单例模式
老歌老听老掉牙2 小时前
Python 模块深度解析:从创建、导入到属性机制
python·模块
2301_795099742 小时前
HTML5中Object标签定义外部资源容器的备份逻辑
jvm·数据库·python
z4424753262 小时前
CSS如何保证移动端顶部Fixed头部的安全区域
jvm·数据库·python
weixin_458580122 小时前
golang如何优化反射性能_golang反射性能优化技巧
jvm·数据库·python
深蓝海拓2 小时前
Qt:创建一套基于HSL颜色体系的颜色库
笔记·python·qt·学习·ui
步辞2 小时前
CSS如何解决小屏幕上的长单词截断版面
jvm·数据库·python