百度之星比赛题目讲解
按照难度排序。
G. Cafeforces
可以钦定第一个账号用来上分,第二个账号用来处理不利于第一个账号上分的比赛,这样问题就能等价转化为只有一个账号,但是可以从 场比赛中选一些来打,问最后评分的最大值。
设当前评分为 ,本场比赛表现分为 ,显然只有 时打比赛才更优。即一场比赛后的评分会变为 。从前到后扫一遍即可。
D. Left and Right
当且仅当 ,答案是 infty。当 时答案为 。
如果 ,枚举你可以将 拆分为的数字个数 ,你能得到无穷多个区间 。你需要求不在任何区间内的正整数 个数。而注意到一旦在某个 满足 时,所有超过 的 都一定能表示;如果让 是满足该性质的最小 取值,那么对于所有小于 ,都有 内的 无法被表示,而这些区间的长度和是一个等差数列求和。单次询问时间复杂度 。
A. Bus Station
如果存在 ,总是能调整为 ,于是对所有需要被统计的集合 ,每个点在 内的出现次数等于其度数模 ,并且容易证明这是最优的。
进一步地,对于一个点 ,考虑所有 的邻边,其中有 条是特殊的(被覆盖路径的一条端点恰好为 ),而剩下的 对可以自由配对,并且对每个结点,覆盖其的所有路径的配对方案都是独立的,于是答案只和所有 有关。具体地,答案是所有 的乘积。时间复杂度 。
C. GCD Xor MEX
考虑对 的值 分类讨论:
- 若 ,则异或和等于 ,直接枚举 的值并更新答案即可。
- 若 ,则必须选择 ,异或和等于 的值异或 ,同样可以枚举 的值。
- 若 ,则选择的数中总是存在 ,所以 总是为 ,枚举 的值更新答案即可。
每次答案更新都形如一个区间和一个数取 ,容易解决这个问题。时间复杂度 。
E. Odd Occurrence
首先考虑整个序列只有一个数。此时长为 的子序列出现了 次,而根据 Lucas 定理的推论,。这意味着 的所有数在二进制下都是 的子集,也就是存在正整数 使得 。所以对于原序列的每种元素,其在原序列中出现次数必须恰好形如 ,否则不合法。
然后考虑整个序列只有两个数。假设一个数是 ,出现了 次;一个数是 ,出现了 次。那么序列中 和 的总出现次数是 。又 都是奇数,所以 是奇数。如果 和 同时出现,那么必然只能是奇数+偶数=奇数,出现了一个偶数,不满足。这意味着 和 不能同时出现,即 和 的下标范围不能有交。从而我们推出,原序列满足条件的充要条件是,每种数出现次数形如 ,并且所有出现位置都是连续的一段。 或 判断即可。
B. Plants vs Zombies
假设当前考虑了 内的陷阱摆布情况,所有剩余的血量 的敌人里,生命值最小为 。那么此时有两种决策:
- 在 内摆放至少一个陷阱。因为可能要摆放很多个陷阱,所以先在位置 摆放是不劣的。因为目前所有剩余的敌人集合就是会踩到这个位置的敌人集合,可以计算出这个陷阱会直接击杀哪些敌人,随后转移到 的状态;
- 在 内不摆放任何陷阱。于是生命值等于 的所有敌人自然死亡,转移到 的状态。
考虑设 表示在给出 个敌人的情况下,计算所有可能情况的时间复杂度。有 。在题目给出的限制条件下, 在可以接受的范围内,于是暴力 dfs 的时间复杂度就是 的。
通过对所有可能到达的状态 BFS 扩展会比暴力 DFS 的效率大概快 10 倍左右,详见给出的 std-2.cpp。但两种做法应该都可以通过。
H. Soupo
直接在 Treap 上维护循环卷积,考虑到插入的期望旋转次数都是 的,考虑到交换律,你可以把插入遍历时的 pushup 换掉改成卷一个两项的多项式,只有在旋转时的 pushup 是需要重新跑一遍 的循环卷积的。
不妨设你的插入删除次数是 而翻转或查询次数是 ,那么你的复杂度是 。
出题人在 std 中使用的是无旋 treap,旋转对应的就是无旋 treap 找到位置后进行分裂的那段路径,因此这样做实际是一样的。
事实上替罪羊树也能做到这个复杂度,因为你对一个大小为 的树重构的时间复杂度为 ,因为大小超过 的子树只有 个,但是我还没有写过这个做法。
F. Operation
以下设 为值域。
首先,我们用在 trie 树刻画操作过程:把这些区间中的数从低位到高位插入 trie 树中,则在这颗 trie 树上 dp 即可求出答案。
显然我们不可能真正将这些数插入。于是我们观察一个区间数的性质。
我们考虑从高位到低位的数位 dp 的过程,则一个区间一定可以划分为 个区间,其中每个区间都形如“前几位固定,后几位任意选 ”的形式。
我们用正则表达式刻画这类区间,容易发现是可以刻画的:前几位确定,后几位是 ,倒着插入后即为“前几位是 ,后几位确定”于是,我们建立正则自动机,在这个自动机上 dp 即可得到答案。
注意到,因为这个正则表达式的特殊性质,我们并不需要跑建立朴素正则自动机的算法。我们考虑这样一个算法来建立自动机:
-
把拆分后得到的 个区间按照 的长度从大到小排序。排序的目的是防止 少的串破坏下文中链状 DAG 的结构。
-
首先,在 trie 上插入一条链状 DAG,形如“ 到 有两条边(), 到 有两条边()等”,长度为最长连续 的长度。
-
然后,每次插入一个串时:
-
首先跳若干次 。
-
然后,按照插入 trie 的方式插入。注意到我们为了保证之前插入的部分和现在插入的部分不会组合出新的串,我们需要可持久化的插入方式,即插入时要把当前要走到的节点复制一份再走。
-
于是就做完了。复杂度 。