CCF 202104-2 邻域均值 python 满分
写在前面:我怀着无比深重的心情写下这篇博文,因为考场上没有做出来o(╥﹏╥)o
题目叙述
问题描述:略
输入格式:略
输出格式:略
样例
样例1输入
4 16 1 6
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
样例1输出
7
样例2输入
11 8 2 2
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 7 0 0 0 7 0 0 7 7 0
7 0 7 0 7 0 7 0 7 0 7
7 0 0 0 7 0 0 0 7 0 7
7 0 0 0 0 7 0 0 7 7 0
7 0 0 0 0 0 7 0 7 0 0
7 0 7 0 7 0 7 0 7 0 0
0 7 0 0 0 7 0 0 7 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
样例2输出
83
满分证明
第一种思路:
第二种思路(可以看出第二种思路占用内存多一些):
解题思路
有两种思路,一种是严格控制数组范围,另一种是周围填充0防止数组越界。
吐槽一下
上一次CCF第二题就是考的前缀和,没想到今年还是前缀和,只不过是从一维前缀和升级为二维前缀和,只怪我并没有真正做到举一反三,总结去年第二题时只是粗略看了一遍,没有抓住其精髓所在(还是对自己要求不严格)。
优化暴力
问题描述有点复杂,如果用暴力穷举会超时(大多数人只能得70分,┭┮﹏┭┮;少部分人拿30分,然鹅我就是那少部分人~),这时候就要想如何去优化了。
- 这个题和201604-2的俄罗斯方块有点类似,为了方便计算,我们在计算矩阵的外围填充0矩阵,防止后面出现数组越界;
- 事后想,这里其实不用0矩阵填充可能会更好做一点,因为当你填充完后你要处理的问题是在原问题基础上多加了扩充矩阵,所以还是不加扩充矩阵可能要好做一点。
- 大家很容易想到穷举,但穷举的时间为n*n*(2*r+1),原地爆炸;
- 此处用到二维前缀和算法(具体内容“知识点学习板块”),简单来讲单独建立一个求和的二维矩阵,用于存储数据,坐标为(x,y)中存储的是从点(0,0)到点(x,y)所形成的矩阵内所有元素之和;避免了单独将原矩阵循环做切片求和处理,减少时间复杂度;
- 巧用元素矩阵之和求区域矩阵和(利用坐标之间的关系);
- 计算领域内元素个数;
- 求领域均值与阈值作比较并计数;
- 输出结果。
难点分析
- 其实如果想到二维前缀和算法而且懂得二维前缀和算法具体如何使用,就已经成功一大半了;
- 计算领域内元素个数,即分析坐标有没有越界(要用r和n与横纵坐标之间的关系)。
注意:
可读性很重要,请对比下面两个表述一致的代码!!!
x_l = x - r if (x - r) > 0 else r
x_r = x + r if (x + r) < (n + r) else n
y_u = y - r if (y - r) > 0 else r
y_d = y + r if (y + r) < (n + r) else n
if (x - r) > 0:
x_l = x - r
else:
x_l = r
if (x + r) < (n + r):
x_r = x + r
else:
x_r = n
if (y - r) > 0:
y_u = y - r
else:
y_u = r
if (y + r) < (n + r):
y_d = y + r
else:
y_d = n
不管你们感觉怎么样,在考场上的我加之心烦意乱,最后把自己给绕晕了!
代码集锦
暴力穷举
n, l, r, t = map(int, input().split())
xs = [[0] * (n + 2 * r) for _ in range(n + 2 * r)]
for i in range(r, n + r):
xs[i] = [0] * r + list(map(int, input().split())) + [0] * r
count = 0
for x in range(r, n + r):
for y in range(r, n + r):
tem = []
for k in range(2 * r + 1):
tem = tem + xs[x - r + k][y - r:y + r + 1]
x_l = x - r if (x - r) > 0 else r
x_r = x + r if (x + r) < (n + r) else n
y_u = y - r if (y - r) > 0 else r
y_d = y + r if (y + r) < (n + r) else n
num = (x_r - x_l + 1) * (y_d - y_u + 1)
if (sum(tem) / num) <= t:
count = count + 1
print(count)
第一种思路(二维前缀和优化)
n, l, r, t = map(int, input().split())
mat = [list(map(int, input().split())) for _ in range(n)]
sum_mat = [[0] * n for _ in range(n)]
out_counts = 0
for i in range(n):
for j in range(n):
if j > 0 and i > 0:
sum_mat[i][j] = mat[i][j] + sum_mat[i][j - 1] + sum_mat[i - 1][j] - sum_mat[i - 1][j - 1]
elif j == 0 and i > 0:
sum_mat[i][j] = mat[i][j] + sum_mat[i - 1][j]
elif i == 0 and j > 0:
sum_mat[i][j] = mat[i][j] + sum_mat[i][j - 1]
else:
sum_mat[i][j] = mat[i][j]
for i in range(n):
for j in range(n):
max_i = i + r if i + r < n else n - 1
min_i = i - r if i - r > 0 else 0
max_j = j + r if j + r < n else n - 1
min_j = j - r if j - r > 0 else 0
count = (max_j - min_j + 1) * (max_i - min_i + 1)
if j - r > 0 and i - r > 0:
ave = (sum_mat[max_i][max_j] - sum_mat[max_i][j - r - 1] - sum_mat[i - r - 1][max_j] +
sum_mat[i - r - 1][j - r - 1]) / count
elif j - r > 0:
ave = (sum_mat[max_i][max_j] - sum_mat[max_i][j - r - 1]) / count
elif i - r > 0:
ave = (sum_mat[max_i][max_j] - sum_mat[i - r - 1][max_j]) / count
else:
ave = sum_mat[max_i][max_j] / count
if ave <= t:
out_counts = out_counts + 1
print(out_counts)
第二种思路(二维前缀和优化)
n, l, r, t = map(int, input().split())
mat = [[0] * (n + 2 * r) for _ in range(n + 2 * r)]
for i in range(r, n + r):
mat[i] = [0] * r + list(map(int, input().split())) + [0] * r
count = 0
#计算二维前缀和
sum_mat = [[0] * (n + 2 * r) for _ in range(n + 2 * r)]
for i in range(r, n + r):
for j in range(r, n + r):
sum_mat[i][j] = mat[i][j] + sum_mat[i][j - 1] + sum_mat[i - 1][j] - sum_mat[i - 1][j - 1]
for i in range(r, n + r):
for j in range(r, n + r):
# 判断领域内元素个数
# 领域x,y最左,最右坐标
min_i = i - r if i - r > r - 1 else r
max_i = i + r if i + r < n + r else n + r - 1
min_j = j - r if j - r > r - 1 else r
max_j = j + r if j + r < n + r else n + r - 1
# print(min_i, min_j, max_i, max_j)
nums = (max_i - min_i + 1) * (max_j - min_j + 1)
# print(nums)
sum_m = sum_mat[max_i][max_j] - sum_mat[i - r - 1][max_j] - sum_mat[max_i][j - r - 1] + \
sum_mat[i - r - 1][j - r - 1]
flag = sum_m / nums
if flag <= t:
count = count + 1
print(count)
知识点学习——二维前缀和
由“一维前缀和”我们知道,运用“一维前缀和”可以计算任意两个区间之间的元素和;其实“二维前缀和”也是如此,“二维前缀和”可以用来计算任意一个子矩形内的元素和。
比如我们要计算红色区域面积:
# sum[x1][y1] 被减掉两次
area_red=sum[x4][y4]-sum[x3][y3]-sum[x2][y2]+sum[x1][y1]
二维前缀和矩阵的生成:
# mat[xc][yc]表示原始矩阵
sum[x_c][y_c]=sum[x_a][y_a]+sum[x_b][y_b]+mat[x_c][y_c]
<==>
sum[x][y]=sum[x-1][y]+sum[x][y-1]+mat[x][y]
感谢及参考博文
部分内容参考以下链接,这里表示感谢 Thanks♪(・ω・)ノ
参考博文1 二维前缀和详解
https://blog.youkuaiyun.com/qq_34990731/article/details/82807870
参考博文2 前缀和(二)
https://blog.youkuaiyun.com/FGY_u/article/details/109390559
需者自取传送门(∩ᄑ_ᄑ)⊃━☆【CCF 2013-2021】本博主整理历年至少前两题 python 满分代码目录