题解:P12876 [蓝桥杯 2025 国 Python A] 杨辉三角
题目简述
记
给定
等价递推变形
观察杨辉三角(数字前标注 * 说明该数字在对称轴上),如下图所示(记为图 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
将杨辉三角逆时针旋转
*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
坐标规定:左上角为
例如:
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 的形式就是第
即
题目给定
暴力做法
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 行)开头两个数字为1 和1 ,到第n 行(y=n-1 行)开头两个数字为1 和n ,根据递推公式下一行前两个数字(坐标为[0,n] 和[1,n] )为1 和n+1 ,必定大于n 。因此只需遍历到第n 行即可。 - 题目要求只统计大于等于
2 的数,遍历x, y 坐标都是从1 开始,因此不会遍历到1 ,也就是无需对num进行额外判断是否大于1 。
结果:
优化一:滚动数组
递推公式只需上一行,矩阵可压缩为两行:
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)
结果:
优化二:利用对称性
考虑到杨辉三角是个对称结构,观察图 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
可以发现,对称轴上的数(数字前标注 * 的数)不仅满足递推公式
也就是说,在
因此,完整的递推公式为:
最终 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)
总结
本题关键知识点:
- 杨辉三角的递推公式与对称性。
- 理解杨辉三角的生成方式,发现并利用其对称结构进行优化。
- 动态规划与滚动数组优化。
- 用动态规划思想递推出所有可能的数,并通过滚动数组降低空间复杂度。
第一次写投稿题解,欢迎指正!