Day 7(文泓宇):
我们将它分成两个部分:
Part 1:NOIP往年的题目(赠送链接)
[NOIP2010] 引水入城
我们可以运用DP的方法:
看完这道题马上想到: 水只往低处流, 直接把每个蓄水池可以到达的地方全部搜出来!
首先看不能覆盖最后一行的情况: 直接从第一行开始 bfs 一遍,
如果最后一行有走不到的, 统计一下数量直接输出.
如果可以覆盖呢?
每个蓄水池走出来的最后一行的点可能是断断续续的, 如果要dp, 状态就会非常难设置. 如果直接状压, 数据范围 500 ......
实际上上面的情况是不会出现的. 如果一个蓄水池走出来的点中间是有一个点断开, 那么这个点对于任意一个蓄水池来说都是走
不到的. 这会在判断无解时排除. 为什么一定走不到?
中间有个点断开了, 但是两侧都可以流到?
那么中间这个点一定会高于两侧的点, 并且也高于上面的点.所以还会有谁会流到它呢?
有了上面这个结论, 对于每一个蓄水池, 它在最后一行所走出来的点一定是一段连续的区间. 把这些区间拿出来, 就变成了经典的线段覆盖问题.
由于数据范围很小, O(n2) 的做法也是可行的:
设 dp[i] 为覆盖1 到 i 的区间所需要的最少线段数. 把每个区间按照 l; r 双关键字排序, 枚举区间转移:
dp【i】=min(dp【j】)+1 (Li<j<Ri)
最后,我们可以直接用 dfs + 记忆化找到这些区间,这部分的时间复杂度 O(nm)。
[NOIP2011] 选择客栈
我们设pre【i】表示位置i之前(包括位置i)最大的满足最低消费的咖啡店位置,pos【j】表示第j种颜色客栈目前所出现的最大的位置,tot【j】表示第j种颜色客栈目前所出现的总数,res【i】表示位置i作为右端点能组成多少组配对。
因此得到了转移方程(c记为i处的颜色):
res【i】=res【pos【c】】,pre【i】<pos【c】.
res【i】=tot【j】,pre【i】>=pos【c】.
[NOIP2011] 观光公交
先考虑没有加速器的情况, 这时可以得到公交车到达每个点的时间:
arrive[i] = max(arrive[i - 1]; wait[i - 1]) + Di-1
注意, 公交车会等到所有的乘客才会出发.
于是就得到了初始答案:
ans = ∑arrive[Bi] - Ti,(i∈【1,m】)
由于加速器会减少总乘车时间, 现在要找的就是用加速器尽量减少更多的乘客的乘车时间, 直接从初始答案中减去即可.
接下来考虑在哪两个点之间加速.
如果公交车到达一个点的时间早于最后一个乘客上车的时间,
那么在到达这个点之前的路上把加速器用光都不能减少目的地在这个点后面的乘客的乘车时间. 他们会被这个点的等待时间限制死了,
这个时候加速器对这些乘客无效.
这样就可以算出每一个间隔会加速多少个乘客.
设 reach[i] 表示在第 i 个点的时候, 加速最远可以影响到目的地为哪个点的乘客.
转移:
reach[i]=reach[i+1],arrive[i+1]>wait[i+1]
reach[i]=i+1,arrive[i+1]≤wait[i+1]
根据贪心的想法, 每次加速器肯定是加速乘客最多的地方. 于是每次选取 max{sum[reach[i]]-sum[i]},(1≤i≤n). 其中 sum[i] 是目的
地为 i 的乘客数前缀和.
找到这个位置, 把它的距离 -1, 然后再从答案中减去这个乘客数, 重复至所有加速器用完或无法加速为止.
注意: 每次修改某条距离后都要重新计算 arrive[ ].
复杂度: O(nk)
[NOIP2012] 开车旅行
题目很长, 但是它只有两个问题, 第二个问题看上去稍微简单一点, 就先看第二个问题吧.
第二问直接给出一个起点, 问 A 和 B 能够走的总路程. 因为每个点的决策是固定的, 那么我们就可以先把每个点向右最近的和第二近的点预处理出来.把所有点按照海拔升序排序, 用链表把它们串起来. 然后按照原来的顺序从左至右遍历每个点.
对于每个点, 在链表上向左找两个, 向右找两个, 就可以找出最近和第二近的点. 处理完这个点, 就把它从链表上删掉, 因为它们只会向右开车, 左边的点不考虑.预处理好这些东西, 对于一次询问就可以直接模拟每次决策算答案了. 但多次询问肯定会 TLE......
会 T 成狗.
所以还需要想出一个更快捷的方法免去暴力判断决策.一次旅行会出现很多决策, 并且一半的决策都是相同的, 全是 A或全是 B. 这个时候就会想到把多个相同的决策全部放在一起计算,
但是 A 和 B 的决策是间隔出现的, 分开计算不现实. 所以把’ A 走一步和 B 走一步’算成一步, 这样每一步的决策就变成一模一样的了.当我们按照这个方法建边时, 这个图就变成了一棵树.
为什么? 因为这样每条边的决策都是一样的, 也就是说一个点走向终点的路径只有一条, 并且它不会回头.是一棵树那就好办多了, 直接倍增, 一次询问复杂度为 O(log n). 倍增在记录祖先信息的同时分别记录 A 和 B 走过的路程.
于是第二问就这么愉快地解决了.
再来看第一问. 它问的是一个起点, 要求只有一个: A 走过的路程比 B 走过的路程比值最小.
既然已经可以倍增了, 把每一个点都试一次不就得到结果了吗.
注意一个细节: 当倍增到限制后, 还要判断一下 A 是不是还可以继续走一步.
总复杂度: O(n log n)
[NOIP2012] 疫情控制
显然每个军队驻扎的节点深度越小, 它能控制的叶子节点越多.
最好的情况就是根节点的儿子全部都驻扎了军队, 那么这样需要的军队数就是最少的了. 但并不一定所有的军队都可以走到根节点的儿子. 题目要求时间最短, 那就直接二分答案嘛. 主要问题就是
怎么判断答案是否可行.
把所有的军队尽量移动到首都, 走不到的就移动到深度最小的地方.
然后把军队驻扎的节点都打上标记. 若如果一个节点的所有儿子或他自己打上了标记, 那么就意味着以这个点为根的子树都已经控制住了. 通过一遍 dfs 把标记上传就可以得到根节点的哪些儿子是没有被控制的了.
于是这个时候只剩下在首都的军队可以移动了.
在首都的军队一定是以最少的时间控制所有还未控制的儿子(他们不能停在根节点), 这里就需要一点贪心的技巧.
把所有军队按照剩余时间降序排序. 那些儿子也按照距离按降序排序.
同时每个儿子记录一下从它这里走向根节点的军队的剩余时间最小值. 因为每个军队都是由儿子走来的, 它可以不走到根节点, 直接停在这个儿子上.
于是每个儿子由经过它的时间最少的军队覆盖. 如果没有军队经过它, 就按之前的排序方法, 拿剩余时间最长的去控制距离最远的.
总复杂度: O((n log n + m log n) log 109)
[NOIP2013] 华容道
最少时间可以联想到最短路, 但是这里在确定起点和终点后, 每次移动都要依赖那个空白格子, 这给转移带来了极大的难度.
首先要解决的问题就是白格子怎么处理. 只有与白格子相邻的棋子可以移动, 移动后它就会和白格子交换位置, 换个角度想, 这就相当于移动了白格子.
一个棋子只能移动到与之相邻的空格上. 这个时候每移动一个棋子, 就相当于先把空格移动到它要移动的方向上, 再移动这个棋子. 得到了比较清晰的转移方式, 于是就可以考虑开搜了. 因为空格所在的位置会影响答案, 所以就要增设一维状态, 表示当前空格在这个格子相邻的哪个方向. 转移的时候先求出移动空格的时间, 以及
移动棋子的时间, 移动后和空格交换位置, 即转移到空格位置相反的状态上.
开始的时候把空格移动到起点的四个方向, 对于这四种初始状态分别搜一次. 这样暴力转移就可以搜出答案. 但是有很多组询问,
如果每次都暴力......
每个询问虽然起点终点和空格位置都是变化的, 但有一样东西一直没有变过——棋盘.
也就是说, 一个状态走到走到另一个状态的距离是固定的, 不受起点终点和空格初始位置影响, 因为状态设置中并没有考虑这些因素. 这样我们就可以在一开始预处理出可以互转移的状态间的距离. 然后就可以直接跑最短路了.
对于每组询问, 直接新增两个节点, 一个连向起点, 距离为移动初始空格的时间; 另一个连向终点, 距离为零. 把这个两点的最短路跑出来就是答案. 记得特判无解.
预处理的时候要注意一个细节: 白格子不能经过当前枚举的格子.
总复杂度: O(q(E + V log V )).
[NOIP2014] 飞扬的小鸟
设 dp[i][j] 表示到达第 i 列, 高度为 j 至少要跳多少次.
转移:
(X i 为跳向第 i 列时可以跳的高度, 注意可以跳多次)
dp[i][j] = max{dp[i - 1][j - k · Xi]} + 1
(也可以不跳, 但只会下落一次, Y i 定义类似)
dp[i][j] = max{dp[i - 1][j + Yi]}
在实现的过程中:
第一种转移有一个系数 k , 直接枚举会造成时间复杂度不对, 直接利用当前这一列非法的格子 (有柱子的地方) 把状态从最底层转移过来, 就省去枚举一维;
因为跳的再高都会在顶端停下, 所以顶端需要特判, 它可以由dp[i - 1][m...m - Xi] 这些状态转移过来, 当然, 还有跳多次的,由当前列转移 (同上).
要先转移完上升的状态再转移下降的状态. 因为上升可以多次,而下降只能一次, 不能通过本列多次转移.
由于利用了非法格子, 在转移完这一列后要把这些格子全部去除.
输出方案直接倒着遍历一次结果就好.
总时间复杂度: O(nm)
[NOIP2015] 运输计划
对于树上路径, 首先可以想到的就是倍增了.
要改变一条树边使得路径总和尽量减小, 简单地来想应该是减去一条权值乘以覆盖路径条数最大的边.
但是这么做就要把每条路径遍历一次, 然后统计出这个值. 显然复杂度是行不通的.
接下来就有一个好的方法: 二分答案.
首先预处理 LCA 倍增数组和每个点到根的距离, 求出所有路径的长度, 按升序排序. 现在二分一个答案, 如果大于当前所有路径的长度, 这个答案显然合法; 如果存在一些路径长度大于二分的答案,
那么现在就必须找到一条边, 被所有大于二分长度的路径覆盖, 并且减去这条边的权值, 每条路径的长度都不超过二分长度, 这时答案也是合法的.
于是现在问题就只剩下找出这条覆盖所有大于二分长度的路径的边了.
这里用到了一个非常实用的标记方法. 把每条大于二分长度的路径在它的两端打上 +1 标记, 在它们的 LCA 的父亲上打上 -1 标记, 最后把所有的标记自底向上相加, 得到的每个点的标记和就是覆盖这个点的路径条数, 复杂度: O(n).总复杂度: O(nlogn)
注意, 此题在题面中说明他要卡常数, 所以一定要注意倍增的实现. 同时遍历树的时候可以预处理 dfs 序, 然后倒序相加, 避免使用dfs.
Part 2:神仙题目
1.
给出一个 n 个数的数列,要求找出一个出现次数大于 n2 的数。
n ≤ 106
空间限制 1 MB。
思路:类似栈的思想,出现一个和之前数相同的就留下,否则丢弃。最后剩下的数就是答案。
2.办公楼
给出一个 n 个点 m 条边的图,求补图的连通块个数。n ≤ 105,m ≤ 106
首先用链表串起每个点。
随便选择一个点加入队列,遍历它的所有相邻点,把它们在链表里标记,并把剩下的点加入队列。重复直到队列空,我们就取出了一个连通块。
接下来再在链表里取出一个点,如果该点被标记就删除它换下一个点。这样可以再取出一个连通块。重复该过程直到链表为空。
时间复杂度 O(n + m) 。
Part1的题倒是还相对可以理解,但是Part2的题是真的神仙啊qwq