noip2017解题报告题解

NOIP 2017 提高组题解

by 杜瑜皓

November 12, 2017

1 math

输出 ab − a − b。因为如果 x, y 是 ax + by = n 的一组解,那么 x + bt, y − at 也是一组解,容易发现最大的不满足的 (x, y) 为 (−1, a − 1),(b − 1, −1) 即 n = ab − a − b 无解。

2 complexity

模拟。首先判定 ERR,因为 ERR 只有 F 和 E 不匹配或者新建的变量与已经存在但未被销毁的变重复两种情况。所以可以使用一个栈,记录一下 F 和 F 对应的变量判定即可。然后是计算复杂度,我们可以把一段代码看成 for 循环嵌套并列结构,然后并列结构又是一堆 for 循环形成的类似于树形的结构。首先考虑并列结构,并列结构就把各个部分的复杂度的指数取 max。然后就是并列结构外面套一层 for 循环,需要一点简单的讨论。1. 如果上下界都是常数,并且下界不超过上界,整个循环体会被执行常数次,所以复杂度的指数不会变。2. 如果上下界都是常数,并且下界超超过上界,那么会执行一次判断,但并不会执行这个循环体,复杂度变成 O(1)。3. 如果上下界都是 n,同 1。4. 如果下界都是 n,上界是常数,同 2。5. 如果上界都是 n,下界是常数,那么整个循环体会被执行 O(n) 遍,时间复杂度的指数加1。可以把树建出来然后用树形 dp 的方法实现,也可以直接在判 ERR 的栈上记录。

3 park

首先求出从 1 号点出发到每个点的最短路,记作 d1(u)。对于一条边 (u, v),我们令w′(u,v) = d1(u)+w(u,v)−d1(v),也就是把每条边的边权更改为 w′(u,v)。因为 d1(u)+w(u,v) ≥ d1(v),1所以新的图边权还是非负的。并且对于从 u 到 v 的任意一条路径,总长度变成了原来的长度+d1(u) − d1(v)。所以变成了我们统计找 1 到 n 号点路径不超过 K 的路径。首先我们来考虑一下如何处理 0 边以及方案无穷大的情况。方案数为无穷大当且仅当存在一个点 u 能通过 0 边走回自己,然后存在一条从 1 到 n 的路径经过了 u 点并且长度不超过 K。所以我们可以求出 n 在新图中沿着反方向到每个点的最短路,记作 d2(u),如果一个点d2(u) > K,也就是这个点得走超过 K 才能到达 n 点,我们可以把这样的点去除。剩下的点到n 号点的距离都不超过 K。也就是说在剩下的图中如果存在一个零边构成的环,那么方案数就是无穷大。这部分的判定只要把所有的零边拿出来,然后进行拓扑排序即可,如果零边形成的图是个有向无环图,那么方案有限。接着考虑转移,记 dp[u][i] 表示从 1 号点到 u 号点距离不超过 i 的方案。我们把 i 作为阶段,先考虑零边,也就是内部的转移。对于零边,我们可以按照拓扑排序得到的拓扑序转移。对于非零边,直接转移到 dp[v][i + w′(u,v)] 即可。时间复杂度 O((n + m)log n + mk)。

4 cheese

我们把每个洞建个点,然后把上下边界建个点。两个点如果可达那么连边。问题就是问上下边界对应的点是不是连通。接着考虑判定两个点不是可达。对于一个洞到上下边界可不可达直接看 z 轴即可。两个洞可不可达只要判定圆心距是不是不超过 2r。注意这里距离的判定建议用整数避免精度问题。

5 treasure

容易发现每层的边对应的权值都不一样,所以可以按层考虑状压 dp。令 dpS,i 表示前 i 层,选的点集为 S,接着枚举 S 补集的子集 S′,作为第 i + 1 层的点,对于 S′ 中每个点,选择一条连向 S 中最小的边。这样直接写的时间复杂度是 O(3nn3) 的。可以加一点优化,比如 S 和 S′ 的代价对于不同的 i 相同,以及可以预处理 u 到任意集合 S 中的最小的边,所以我们可以按 S 从小带大 dp,加这两个优化之后时间复杂度为 O(3nn)。搜索加剪枝也可以得到很高的分数,甚至满分。

6 phalanx

首先我们把最后一列单独考虑。我们观察每个操作,可以分解成在当前行中间删除一个元素,然后在末尾插入一个元素。然后在最后一列中间删除一个元素,在末尾插入一个元素。这个过程可以使用平衡树直接维护,时间复杂度 O(n log n)。我们考虑怎么精妙地实现这个过程,首先我们把最后一列单独的看成一个对象,每个行看成一个对象。对于每个单独的对象,要执行的过程都是中间删除一个元素,末尾插入一个元素。要在线实现这个过程是很困难的,我们先考虑怎么离线做这个事情。离线做最大的问题是你不知道末尾插入的元素是什么。不过我们可以建立一个虚拟的元素,表示这个元素是某次操2作出来的答案。最后所有的操作都结束之后,再看一下这些虚拟的元素对应的什么,然后输出即可。所以问题转化为了单个对象的操作,假设有 Q 个操作,我们希望能在 O(Q log n) 的时间复杂度内处理这些操作,并且每个对象初始化是一个 1 到 n − 1 的列表 (对最后一列是 1 到 n)。为了避免使用平衡树,我们将这个考虑成一个静态的数组 A 存储每个位置的元素,并且用一个数组 B,其中每个数都是 0/1 表示有没有这个元素是否还未被删除。这个时候在末尾插入元素的操作就只要把这个静态数组末尾复制,然后把 B 数组对应的位置改成 1。而删除操作需要首先算出前缀和为 k 的位置,然后把 B 这个位置改成 0。这些操作都可以使用树状数组解决,因为树状数组本来就有个树的结构,所以可以直接在树状数组上二分并且统计,时间复杂度是 O(log n) 的。现在问题还有初始化,我们需要一个 1 到 n − 1 的数组 A 和全是 1 的数组 B。有两种解决方法,一种是每个对象做完之后把树状数组上的修改全部撤销。一种是把一开始初始化的树状数组备份,并且给每个位置记一个时间戳。每次访问一个位置的时候我们先看时间戳,如果不对的话,也就是在这个对象处理的时候这个位置没有初始化过,就先把这个位置的值从一开始的备份里拷贝过来,然后更新时间戳。总的时间复杂度 O(n log n)。

你可能感兴趣的:(noip)