惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
V2EX
C
Check Point Blog
博客园_首页
B
Blog
D
Docker
U
Unit 42
量子位
I
InfoQ
有赞技术团队
有赞技术团队
Martin Fowler
Martin Fowler
GbyAI
GbyAI
L
LangChain Blog
云风的 BLOG
云风的 BLOG
博客园 - Franky
美团技术团队
T
The Blog of Author Tim Ferriss
阮一峰的网络日志
阮一峰的网络日志
月光博客
月光博客
Vercel News
Vercel News
Recent Announcements
Recent Announcements
雷峰网
雷峰网
大猫的无限游戏
大猫的无限游戏
小众软件
小众软件
Google DeepMind News
Google DeepMind News

皓子的小站

网站字体应用之坑——font-family 篇 糟糕的 meta name="theme-color" How to Automatically Track Newly Supported ESLint Rules in Oxlint How to Gradually Migrate from ESLint to Oxlint (Without Breaking Everything) 静态资源预压缩:零运行时开销,极致节省带宽 CVE-2025-70886 Proof of Concept (PoC) | A Script to Crash Halo CMS Comment Backend 自定义网页鼠标指针——一段曲折的旅程 CVE-2025-70886 漏洞复现/PoC | 一个脚本让 Halo CMS 评论后台瘫痪 2025 年终总结 & 博客两周年 老用户专享!已有 Halo 专业版授权可免费升级商业版 自动追踪 Oxlint 对 ESLint 规则的新增支持 Halo 贡献者证书与实体周边盲盒开箱 博客评论系统指南 CDN 回源跟随配置导致登录异常问题排查 SCDN 免费赞助计划:助力高质量博客&博客圈 锐捷校园网:网络共享与带宽叠加方案(哈理工案例) ESLint 到 Oxlint 渐进式迁移快速上手指南 Fixing Vite Breaking Inline JS & CSS in Thymeleaf Templates 解决 Vite 破坏 Thymeleaf 模板内联 JS & CSS 的方法 我的博客,为什么是月更? 博客俱乐部一周年纪念品开箱 网站字体加载之坑——format 篇 经历 1000000000 次 DDoS 请求攻击后,我总结了三条经验 题解分享:[AtCoder Beginner Contest 414 E] Count A%B=C FiF口语训练破解刷分教程(适用于 Windows) 不要与蠢人辩论 小心网络地雷·续篇 当心网络组织“开往”·续篇 当心网络组织“开往” 小心网络地雷
题解分享:[蓝桥...
HowieHz · 2025-06-18 · via 皓子的小站

题目链接:P12876 [蓝桥杯 2025 国 Python A] 杨辉三角 - 洛谷
首发:题解:P12876 [蓝桥杯 2025 国 Python A] 杨辉三角 - 洛谷专栏
附言:第一次参加蓝桥杯国赛,希望不要打铁。第一次在洛谷提交题解,希望能过审。(6 月 20 日晚上过审了!)

题目简述

f(x) 为正整数 x 在杨辉三角中出现的次数。题目要求:
给定 n,枚举 2 \leq x \leq n,统计不同的 f(x) 各自出现了多少次。

等价递推变形

观察杨辉三角(数字前标注 * 说明该数字在对称轴上),如下图所示(记为图 A):

                           *1
                         1   1
                       1   *2   1
                     1   3   3   1
                   1   4   *6   4   1
                1   5   10   10   5   1
              1   6   15   *20   15   6   1
           1   7   21   35   35   21   7   1
         1   8   28   56   *70   56   28   8   1        
     1   9   36   84   126   126   84   36   9   1     
1   10   45   120   210   *252   210   120   45   10   1

将杨辉三角逆时针旋转 45^\circ,得到图 B:

*1   1   1   1  1  1  1   1   1   1
 1  *2   3   4  5  6  7   8   9  10
 1   3  *6  10 15 21 28  36  45  55
 1   4  10 *20 35 56 84 120 165 220

坐标规定:左上角为 (0,0),向右为 x 轴正方向,向下为 y 轴正方向。
例如:

1(0,0)  1(1,0) 1(2,0)  
1(0,1)  2(1,1) 3(2,1)  
1(0,2)  3(1,1) 4(2,2)

杨辉三角每一行开头和结尾的数是 1;每一行中间的数字等于其“肩”上两数的和。
转换成图 B 的形式就是第 1 行(y=0 行)和第 1 列(x=0 列)的数都是 1;坐标为 (x,y)x \neq 0y \neq 0)的数等于 (x-1,y)(x,y-1) 的和。

\text{M}[x][y] = \text{M}[x-1][y] + \text{M}[x][y-1]

题目给定 n,要求我们统计 2 \leq x \leq n 的整数 x 在杨辉三角出现的次数 f(x),并统计不同 f(x) 值的出现次数。只需递推并统计即可。

暴力做法

from collections import defaultdict

n = int(input())
M = [[1] * n for _ in range(n)]

# count[x] 表示 x 在杨辉三角出现的次数
# 即 f(x) = count[x]
count = defaultdict(int)

for i in range(1, n):
    for j in range(1, n):
        num = M[i][j] = M[i - 1][j] + M[i][j - 1]
        if num > n:
            break
        else:
            count[num] += 1

# stat[n] 表示有多少个 x 使 f(x) = n
stat = defaultdict(int)
for x in range(2, n + 1):
    stat[count[x]] += 1

# 字典自动根据 k 排序,无需额外排序后输出
for k, v in stat.items():
    print(k, v)

注释:

  • 1 行(y=0 行)开头两个数字为 11,到第 n 行(y=n-1 行)开头两个数字为 1n,根据递推公式下一行前两个数字(坐标为 [0,n][1,n])为 1n+1,必定大于 n。因此只需遍历到第 n 行即可。
  • 题目要求只统计大于等于 2 的数,遍历 x, y 坐标都是从 1 开始,因此不会遍历到 1,也就是无需对 num 进行额外判断是否大于 1

结果: 50 分,剩下的点 MLE。

优化一:滚动数组

递推公式只需上一行,矩阵可压缩为两行:

from collections import defaultdict

n = int(input())
M = [[1] * n for _ in range(2)]

# count[x] 表示 x 在杨辉三角出现的次数
# 即题目 f(x) = count[x]
count = defaultdict(int)

for _ in range(1, n):
    for j in range(1, n):
        num = M[1][j] = M[0][j] + M[1][j - 1]
        if num > n:
            break
        else:
            count[num] += 1
    
    M[0] = M[1].copy()

# stat[n] 表示有多少个 x 使 f(x) = n
stat = defaultdict(int)
for x in range(2, n + 1):
    stat[count[x]] += 1

# 字典自动根据 k 排序,无需额外排序后输出
for k, v in stat.items():
    print(k, v)

结果: 60 分,剩下的点 TLE。

优化二:利用对称性

考虑到杨辉三角是个对称结构,观察图 A 可知,对称轴上的数“肩”上两数是相同的。

再次观察图 B(数字前标注 * 说明这个数字在对称轴上):

*1   1   1   1  1  1  1   1   1   1
 1  *2   3   4  5  6  7   8   9  10
 1   3  *6  10 15 21 28  36  45  55
 1   4  10 *20 35 56 84 120 165 220

可以发现,对称轴上的数(数字前标注 * 的数)不仅满足递推公式 \text{M}[x][y] = \text{M}[x-1][y] + \text{M}[x][y-1] ,还满足 \text{M}[x][y] = \text{M}[x][y-1] \times 2

也就是说,在 y = i (i \ge 1) 的行,可以直接从 \text{M}[i][i] 开始递推,无需计算前 i 个数。

因此,完整的递推公式为:

\text{M}[x][y] = \begin{cases} 1 \quad (x = 0 \text{ 或 } y = 0) \\ \text{M}[x][y-1] \times 2 \quad (x > 1,\, y > 1,\, x = y) \\ \text{M}[x-1][y] + \text{M}[x][y-1] \quad (x > 1,\, y > 1,\, x \neq y) \end{cases}

最终 AC 代码如下:

from collections import defaultdict

n = int(input())
M = [[1] * n for _ in range(2)]

# count[x] 表示 x 在杨辉三角出现的次数
# 即题目 f(x) = count[x]
count = defaultdict(int)

for i in range(1, n):
    num = M[1][i] = M[0][i] * 2
    if num > n:
        break  # 对称轴上的都比 n 大了,这一行后续的数必定比 n 更大,下一行的也必定比 n 更大,因此直接结束
    else:
        count[num] += 1  # 此处 num 为对称轴上的数

    for j in range(i + 1, n):
        num = M[1][j] = M[1][j - 1] + M[0][j]
        if num > n:
            break  # num 比 n 大了,这一行后面的也必定比 n 大,结束这一行
        else:
            count[num] += 2  # 此处 num 为非对称轴上的数

    M[0] = M[1].copy()

# stat[n] 表示有多少个 x 使 f(x) = n
stat = defaultdict(int)
for x in range(2, n + 1):
    stat[count[x]] += 1

# 字典自动根据 k 排序,无需额外排序后输出
for k, v in stat.items():
    print(k, v)

总结

本题关键知识点:

  1. 杨辉三角的递推公式与对称性。
    • 理解杨辉三角的生成方式,发现并利用其对称结构进行优化。
  2. 动态规划与滚动数组优化。
    • 用动态规划思想递推出所有可能的数,并通过滚动数组降低空间复杂度。

感谢阅读。鄙人不才,欢迎在评论区指正!