1. 题目里那句“在你窗外”究竟翻译成什么数据结构
每年备考 GESP C++ 五级的学生,几乎都会被群里丢过来的 luogu-P3353 “在你窗外闪耀的星星”折磨一下。这道题代码量很短,短到很多同学以为它是签到题;可它真正考的是能不能把“窗外有多少颗星星”准确翻译成一段连续区间求和,翻译错一个下标,就是接二连三的 WA。准备考 GESP 五级的同学,如果你正在学前缀和、数组下标处理、枚举优化,那这道题就是一块非常标准的试金石。
先把最常见版本的题意剥给你看:天空被简化成一条一维坐标轴,星星是一个个点,每颗星星有两个属性,坐标 x 和亮度 b。你可以移动一扇窗户,窗户的宽度固定为 w。问窗户放在什么位置时,能看见的星星亮度之和最大。同一个坐标上可能同时有多颗星星,它们会全部被看见,亮度按加法叠加。
我上课的时候喜欢让学生先别写代码,而是把这个场景翻译成数据结构语言。一颗星星用一个点表示,那整条坐标轴其实就是一个桶数组 star[],下标是坐标位置,值是这个位置上所有星星的亮度总和。窗户是一段固定长度的连续区间,移动窗户就是枚举这段区间的左端点。题目答案就是:在所有可能的窗口区间里,找一个区间和最大的值。
这道题适合所有正在备考 GESP 五级,或者刚开始准备入门信息学竞赛的 C++ 学习者。因为它的难点不在语法,而在“模型转换”和“边界处理”这两个非常容易被忽略的环节。你哪怕只会数组和循环,也能读懂暴力的写法;但如果你想拿满分,就必须把复杂度从两层循环优化到一层循环。下面我把整个分析过程完整走一遍,包括我平时带学生时反复强调的易错点。
1.1 去掉包装后的数学模型
如果你只在洛谷上读过题面,很容易被“窗外”“星星”“闪耀”这些词带偏,觉得这是一道模拟题。其实它和天文、坐标轴没有任何关系,核心模型非常干净:
给定一个长度为 N 的一维数组 star[],star[i] 表示位置 i 上的亮度总和。给定整数 w,表示窗户可以覆盖一段连续区间。设窗户左端放在 l,右端放在 r,则满足 r - l = w(这是“宽度差”的常见定义)。请你求:
max( star[l] + star[l+1] + ... + star[r] )
看到这个式子,你就应该有一种直觉:这是典型的“连续区间求和”问题。连续区间求和有几个常用的工具,最基本的就是前缀和。前缀和不是靠背代码学会的,你得理解它到底在算什么,才能在不同题目里灵活迁移。
注意,这里我故意用了 r - l = w 这个表述,也就是窗户左端在 l、右端在 l+w。这样窗户覆盖的整数坐标点是 l 到 l+w 共 w+1 个点。为什么单独强调这一点?因为你在网上搜题解、或自己写的时候,经常看到有人写 l+w-1,有人写 l+w,两边吵得不可开交。实际上两个版本可能都对,取决于原题把“宽度”定义成什么。这个问题非常影响 AC,我放到第三章用一整节讲清楚。你先记住这个边界矛盾的存在,写代码之前一定要确认题意口径。
1.2 三个容易在读题阶段埋下的隐患
这道题表面是“星星”,实际是数组,所以有三个读题阶段就会埋下的坑,我建议你现在就形成条件反射。
第一个坑:同一个坐标可能有多颗星星。你如果在读入时直接写 star[x] = b,后读入的星星会把之前的覆盖掉。正确写法是 star[x] += b。这个道理和统计一个班级里每个分数段有多少人是一样的,同一个分数可能有多个学生,应该累加计数,而不是赋值。很多学生不是不会,而是读题时默认了坐标互不相同,结果样例碰巧没有重复坐标,一提交就挂。
第二个坑:坐标不是从 1 开始,甚至有可能是 0。C++ 数组的下标是从 0 开始的,前缀和公式我们用 pre[r] - pre[l-1],如果 l 取 0,就会出现 pre[-1] 这种非法访问。标准做法是把所有坐标统一偏移一位,读入 x 后放到 star[x+1] 里。这样原坐标 0 变成了数组下标 1,原坐标 1 变成了数组下标 2,所有坐标都往右挪一格。窗口的宽度不会因为整体平移而改变,因为窗口左端点 l 也跟着在一个连续的整数范围内枚举。
第三个坑:坐标范围和星星个数不是一回事。星星可能只有 n 颗,但坐标最大值可能到 100000 甚至更大。有的同学拿到题目,一看到 n 很小,就想用“给星星按坐标排序,然后维护滑动窗口”之类的做法。思路没错,但如果你没有正确理解“数组下标是坐标而不是输入顺序”,后面会非常痛苦。最稳妥的做法就是开一个坐标最大值那么大的桶数组,而不是开一个 n
