NOIP & CSP 真题分析
P7073 「CSPJ2020」表达式
先把后缀表达式树建出来,其中取反运算符只有一个儿子,另外两种运算符均有两个儿子。
每次只修改一个变量,也就是改变某个叶子的值,判断根结点的值是否会发生变化。
分析三种运算符:
- 对于与运算符,若其一个儿子值为 \(0\),那么另一个儿子更改不会改变它的值,反之会改变它的值;
- 对于或运算符,若其一个儿子值为 \(1\),那么另一个儿子更改不会改变它的值,反之会改变它的值;
- 对于非运算符,其唯一儿子更改会改变它的值。
在 DFS 计算初始值的时候记录,如果在叶子到根的路径上有任意一个结点是儿子更改不会改变的话,就说明该叶子的值无关紧要。否则更改该叶子的值会改变整个表达式的值。
这样就做到了 \(O(1)\) 查询。
P5660 「CSPJ2019」数字游戏
长度只有 \(8\) 位,每个 \(1\) 又可以单独拆出来作为 \(9\) 的倍数加 \(1\),所以直接输出 \(s\bmod 9\) 即可。
P5017 「NOIP2018PJ」摆渡车
这类最优化的问题要么贪心要么 DP,没有明显性质的一般先考虑 DP。
影响时间的因素是每个同学的出发时间,考虑在时间轴上 DP。在等的同学都是一样的,我们只关心当前有几个同学在等,以及摆渡车出发了多久。但摆渡车出发多久记下来显然不合适,不妨以摆渡车往返一趟的时间 \(m\) 为单位转移。
令 \(f_{i,j}\) 表示到第 \(i\) 分钟,有 \(j\) 个同学在等,此时摆渡车位于起点的最小已经等车时间之和。
转移要么停着等更多同学,要么就出发往返一趟。出发往返一趟的过程中可能有同学陆续抵达起点,也算入等待时间。我们只要知道这一段时间内同学的抵达数量以及抵达时间之和就可以算出一共等待了多久,前缀和维护即可。
时间复杂度 \(O(n\max\{t\})\),获得 \(50\) 分,考场上已经很强了。
容易发现人数和往返时间都很小,很多时间都在等待做无用功。当相邻两个人的抵达时间间隔达到 \(2m-1\) 时,前面抵达的所有人肯定已经坐车走了,因为摆渡车无论如何都有充足的时间往返一趟。
所以我们可以直接把无用的等待时间删除,把后面所有的时间集体前移。这样时间轴长度只有 \(O(nm)\),时间复杂度 \(O(n^2m)\) 足以拿到满分。
注意这部分数据存在 \(t_i=0\) 的情况,初始化第 \(0\) 分钟可能有人。
继续优化。时间轴无论如何还是有必须要考虑的长度的,那不妨直接抛弃时间轴,以人为状态转移。但这样就必须要记录摆渡车出发了多久,所以令 \(f_{i,j}\) 表示到第 \(i\) 个人,摆渡车已经出发了 \(j\) 分钟的最小已经等车时间之和。当 \(m<j<2m\) 时,表示摆渡车已经在起点等待了 \(j-m\) 分钟。
我们可以根据摆渡车已经出发的时间得出当前等待的人数。摆渡车要么是在某个人抵达后立刻出发,要么是在相邻两人之间回到起点后立刻出发。时间复杂度 \(O(n^2+nm)\)。
P5018 「NOIP2018PJ」对称二叉树
暴力就递归判断当前子树是否是对称二叉树,如果是就取 \(\max\) 然后退出,否则继续递归左结点的左子树和右结点的右子树、左结点的右子树和右结点的左子树判断。
如果一次判断在遍历 \(x\) 个结点后结束了,那么左右子树内都至少有 \(x\) 个结点。\(T(n)=T(n-x-y)+T(x+y)+O(x)\),时间复杂度 \(O(n\log n)\)。
P9749 「CSPJ2023」公路
从前往后枚举站点驾车,当油不够用时,肯定可以在前缀中油价最低的那个站点提前加上。
P9751 「CSPJ2023」旅游巴士
二分后反图 BFS 和直接 Dijkstra 两种做法题解已经讲得很清楚了。
总共只有 \(n\times k\) 种状态,而边权均为 \(1\),所以最短路最长只有 \(n\times k\)。加上开放时间,二分的上界设为 \(\lceil\frac{\max\{a\}}{k}\rceil+n\) 即可。
P8814 「CSPJ2022」解密
这是一个一元二次方程,二分或者公式求解,两个解即为 \(p\) 和 \(q\)。因为数据保证 \(m=n-e\times d+2\) 为正整数,所以解不可能为负数,只需判断解是否为整数。
P8815 「CSPJ2022」逻辑表达式
输入的字符串是中缀表达式,而「短路」通过后缀表达式能很方便地统计。
暴力的想法是模拟这个过程,每次根据当前区间优先级最高的运算符把区间分成若干段然后递归,时间复杂度 \(O(|s|^2)\)。
考虑优化,预处理出每个左括号对应的右括号位置,这样在寻找优先级最高的运算符的过程中遇到被括号包含的区间就可以直接跳过,因为括号内的表达式在当前层被看做值。
容易发现,对于每一种运算符,我们寻找其所有位置所花费时间的总和不会超过 \(O(|s|)\)。而运算符只有两种,所以总时间复杂度与之相同。
P8816 「CSPJ2022」上升点列
看到单调不减考虑 DP。答案的形式显然是选择最初的一些点,添加一些点将它们连起来,剩下没用完的点添加到最前面或最后面。
将点按两个坐标分别作为第一和第二关键字排序后,令 \(f_{i,j}\) 表示强制选第 \(i\) 个点,前 \(i\) 个点中总共选了 \(j\) 个点,中间添加的点数。转移枚举上一个选的点,时间复杂度 \(O(n^3)\)。
利用状态和值对调的套路可以做到 \(O(n^2k)\)。即令 \(f_{i,j}\) 表示强制选第 \(i\) 个点,中间至多添加 \(j\) 个点,最多能在原点集中选择的点数。转移不赘述。
P7911 「CSPJ2021」网络连接
码量很小但有细节的一道模拟。
建立连接和加入连接都可以用 map 简单实现,重点在于判断地址串是否符合规范。
用一个变量记录当前用了几个 \(\texttt.\) 和 \(\texttt#\)。容易遗漏的地方是开头结尾以及符号之间是否有数字。
此外数字大小要边做边判,否则太长会溢出。
P7912 「CSPJ2021」小熊的果篮
只有删除操作,并且只查询「块」最左侧的水果,容易想到「块」内部用单向链表维护。
每次删除后会合并相邻相同的「块」,把前一个「块」表尾的指针指向后一个「块」的表头。
额外用 vector 记录当前所有「块」的表头和表尾即可,删除后重置 vector。一个「块」只会被操作其大小次,所以时间复杂度为 \(O(n)\)。

浙公网安备 33010602011771号