鸽子固定器
from AprilGrimoire, 题解 by AprilGrimoire
这次的题目顺序真是十分抱歉QwQ A题可能是三道题中AC人数最少的了……
然而这题std才50行,放B和C显得不太合适哇QwQ
子任务一
时间复杂度:
期望得分:
子任务二
容易发现,如果我们确定了选择的固定器大小的范围,那么一定会选择这些固定器中牢固度最大的至多
时间复杂度:
期望得分:
子任务三
当
时间复杂度:
期望得分:
结合子任务二的做法可以获得
子任务四
考虑子任务二的一个优化:在扫描过程中,如果发现右端点被出堆了就终止扫描。这样做显然是对的,因为右端点被出堆后的方案可以通过简单地左移右端点来使答案变优,一定不是最优值。然而,对于构造的数据,这个优化可能并没有什么用:
200000 50 2 2
1 1
2 2
3 3
...
对于这样的数据,这个优化并没有什么用。然而对于随机数据,它还是跑得挺快的。让我们进行一波有理有据的分析:对于第
时间复杂度:
期望得分:
结合前三个子任务的做法可以获得
子任务五
假设以
.
.
.
200000 50 2 2
1 5000
2 1
3 1
...
199999 1
200000 10000000
对于中间的大部分点,最优决策都是选连续的
这个做法实际上可以通过前五个子任务,因为小数据不太好卡,对于随机数据正确性又很高。
实际上冷静一下就能发现决策单调性是假的:如果复杂度和
时间复杂度:
期望得分:
子任务六
考虑优化子任务四的做法。这个做法会被价值递增的数据给卡掉。那么,如果我们二分ST表来找下一个会被入堆的点呢?事实上,这个算法是能通过全部数据的。
证明:假设在以
入堆操作最多会被执行
时间复杂度:
期望得分:
神奇的做法
在比赛时,发现了这样一种神奇的做法: 按照坚固度从小到大考虑所有固定器。 1. 如果当前固定器被选中了,那么选中的固定器集合一定是一个区间。(否则可以把当前固定器换掉来获得更优的答案。) 2. 如果当前固定器没被选中,我们可以把它删掉。
用链表维护剩下的固定器。包含当前最小固定器的区间最多只有
子任务六的两个优化
- 用链表维护当前可能被入堆的点集。如果在从右往左扫的过程中一个点没有被入堆,那么我们可以把它从点集里删掉,因为它以后也永远不会入堆。这样就能避免二分ST表,只有
的复杂度。( 是因为需要用堆维护当前被选的固定器。) - 堆也不是必要的,因为每次可能入堆的点只会增加一个,所以可以线性维护当前被选中的点集。这样就能做到
。
To Do Tree
对于大多数OIer来说,这题应该是个大胆猜结论题。
算法零:构造
看过题的同学就知道,subtask3就是送分的,每星期只能肝一个ddl,搞什么都行嘛……只要输出格式没有问题,大家应该都能得到这
算法一:搜索
直接枚举每星期肝的ddl集合搜索,期望得分
结合算法零可以得到
算法二:子集dp
设
时间复杂度
结合算法零可以得到
算法三:构造
这个题看起来不可做又看起来很可做,有哪些选手能证明一下自己的算法是最优的呢?
先说出题人Mike给出的一种构造算法和证明。
首先对把依赖关系翻转一下,对于任意一个任务
直接说结论:每次选择最深的叶节点执行,若有多个则随意执行一个即可。
证明如下:
考虑答案的两个显然的下界,
显然前面那个是机器数较多的情况,后面那个是机器数较少的情况。
然后将节点按深度分两块,任取一个深度
显然最早在时刻
下面证明
考虑最大值时
上式显然成立,否则
根据当前树的深度是否小于
下面证明,假设深度不小于
首先归纳证明,算法执行过程中,任意时刻树中深度最大的任意
形式化的表述是,设树的深度为
也就是和上面一样的形式。
采用归纳法,反证,假设经过了
根据假设,显然有
考虑此时深度为
删掉一个节点后,深度不小于
则上次深度不小于
即上次深度不小于
由命题
下面证明删掉深度不小于
由
否则
下面证明,算法执行前半部分的任意时刻,设当前树的高度为
使用归纳法,反证,假设经过
这个单位时间内显然删掉了至少一个深度为
这说明之前深度大于
若
若
由于深度大于
其中
深度不少于
因此得到
又
因此当深度大于
综上,算法正确性和用时下界都得到了证明。
这个算法可以做到
按深度贪心也是正确的,证明主要思路采用调整法;按大小贪心也是正确的,证明主要思路采用反证法。证明繁杂赛后我在uoj博客发一下证明。
配对树
from C_SUNSHINE, 题解 by C_SUNSHINE
子任务 1
我们可以暴力枚举ddl区间,然后在树上做树形DP来计算最小匹配的大小,如何DP呢?
令
接着发现其实
时间复杂度
子任务 2
我们接着观察,对于一个确定的ddl区间,一条树边被匹配包含了当且仅当把它删去后树被分成两个包含奇数个ddl的块。若我们令
我们可以换一种方式思考,计算一条树边被多少个偶区间覆盖。
枚举一条树边,把它子树内的ddl全部在序列上标为
对
把序列转化成
子任务 3
这个直接把不超过
子任务 4
我们可以考虑使用线段树来维护序列转化成的
既然单次修改
子任务 5
这条件其实没什么特别的用处,只是让点的分步均匀一些。
子任务 6
树是随机的,我们依旧暴力枚举树边然后只添加子树,可以发现每个点会被添加恰好深度次,而深度的期望是
子任务 7
在本来的做法中,我们对子树做完后,清空线段树再吧子树内的 ddl 重新加入,可以发现这个过程中有重复计算。考虑对于每个点
到这里其实算法已经很显然了,对树轻重链剖分,
因为NOI之前信心赛大家开心就好,所以两个
这题我本来出出来准备当个前两题的难度的,但不知道哪个管理员就把它挂到 C 题上了,我给他题解的时候还把这段话删了,所以两个
想到用线段树维护后,本题有个较为直接的做法即采用线段树合并,每个点的线段树可以看成其所有子树并起来再插入其上的 ddl,我们知道
你要问我为啥就是个 Splay 合并却写这么长题解,我只能说……既然管理员钦点了,得装的像个 C 题的样子(逃