Atcoder Beginner Contest 270(A~Ex)
Good news : Finished A~F in 44 minutes.
Bad news : WA 3 times for A~F and the solution of G is FST.
赛时
A 简单或运算。
B 简单分类讨论(少带了个绝对值挂了一发谔谔)。
C 找树上两点路径,甚至不需要优化。
D 刚开始以为贪心,双方直接取最大值即可,于是挂了两发。但是由于决策有限且不连续,取最大值的决策不一定是最优的。所以我们还是需要 dp。
不妨设 \(dp_{i}\) 表示取完 \(i\) 个石子之后最后取的人比另一个人多取几个石子。则有转移方程:
由于石子总数固定,我们又知道了先手会比后手多取的石子数量,于是最终答案就是 \(\frac{dp_n+n}{2}\)。
E 小清新题。我们不妨将 \(A\) 看成一个柱状图,则每一轮其实就是从下向上将整个图截断一个单位长度。于是我们可以考虑用一个扫描线从下向上扫,以每一个柱子的高度为关键高度动态记录贡献,最后找到无法再向上扫的位置,暴力轮一遍即可。时间复杂度 \(O(n)\)。也可以用二分答案代替从下向上扫,将扫描的复杂度降低为 log。不过复杂度瓶颈不在这里,没有必要。
F 最小生成树,考虑将船坞和机场分别作为一个虚拟节点,对于【不建造船坞也不建造机场】,【建造船坞不建造机场】,【建造机场不建造船坞】,【机场和船坞都建造】分为四种情况讨论,分别求一遍最小生成树,最后取最小值即为最终答案。
G 刚开始没注意到取模,以为 \(x_i\) 单调递增,还 20min 写了个矩阵倍增查找,写完发现样例过不去,于是 FST 喜加一。后来又觉得复杂度实在不太好优化,又想向根号分治之类的方向想,但也没什么结果。
赛后
G - Sequence in mod P
G 正解还真的沾点根号!
考虑将式子展开:
我们把所有和 \(A_i\) 有关的项全部放到右边,剩下的扔到左边,将等式两边同时乘上 \(A-1\) 再加 \(B\),则有:
左边的常数看作 \(x\),右边的常数看作 \(y\),则有:
也就是一个经典的高次同余方程。我们可以采用 BSGS 算法求解。
哈希用 map 实现,时间复杂度 \(O(T\sqrt{P}\log \sqrt{P})\)。
需要注意当 \(A=1\) 的时候,上面的式子是不成立的,我们会得到:
此时 \(i=\frac{G-S}{B}\),作个逆元即可。同样此时也需要判断 \(B\) 是否为 \(0\),如果为 \(0\) 则需要进一步特判。
除此之外还需要特判一下 \(X_0=S\) 是否等于 \(G\),以及 \(A\) 是否为 \(0\)。
总结来说,本题的本质实际上就是根号优化,细拆 BSGS 根号优化的本质其实就是一个 meet in the middle。\(X_i\) 显然是不好做的,因为我们无法将两个 \(X_i\) 的答案拼起来得到另一个 \(X_i\),因为这个式子不满足结合律。于是我们将 \(X_i\) 的式子拆开,将高次幂项和其它项分离,发现可以化成一个满足结合律的高次同余方程的形式,于是就可以使用 BSGS 进行根号优化。
Ex - add 1
H 也尝试补了一下。感觉很妙。
题目中的状态是复杂的,且 \(n\) 的范围很大,无法状压。但是我们是需要进行状态之间的转移的。
所以我们就需要考虑如何才能高效地表示一个状态。我们都关注哪些信息呢?每个数当前距离 \(a_i\) 的差值?这太冗余了。事实上,由于本题特殊的性质,我们只需要关注 \(n\) 个数中,当前值 \(c_i\) 距离 \(a_i\) 最大的差值即可。
很奇怪?但是这却是可以转移的。设这个最大的差值为 \(s\),我们显然可以找到一个 \(r\),使得 \(a_r<s\le a_{r+1}\)。此时如果我们对于 \(a_1\) 到 \(a_r\) 进行之间的某一个数进行了清零操作,由于其他数的值都会加 \(1\),所以我们都 \(s\) 就会被更新为 \(s-1\);如果我们对于 \(a_{r+1}\) 到 \(a_n\) 中的录一个数 \(a_k\) 进行了清零操作,那么由于 \(a_k\ge s\),所以此时 \(s\) 就会变成 \(a_k\)。
发现了吗?这个状态虽然简洁,但是在找到 \(r\) 的情况下,是可以进行转移的。(其实这里用一个简单的 \(s\) 映射一类复杂的状态的设计,有点类似于 hash 的感觉了,个人感觉非常妙)
于是我们设 \(dp_i\) 表示当前 \(s=i\) 时,距离终点最大差值为 \(0\) 状态的期望轮数(倒序转移),则初始状态 \(dp_0=0\)。有转移方程:
我们转移顺序变一致一下,把 \(dp_{i-1}\) 拉出来:
然后我们发现此时 \(i\) 较小的状态是由较大的状态转移过来的,而初始状态好像是 \(dp_0\),顺序反了。于是我们考虑将 \(dp\) 的意义颠倒一下,设 \(f_i\) 表示 \(dp_{a_n}-dp_i\),则有 \(dp_i=dp_{a_n}-f_i\)。考虑将转移方程化为只和 \(f\) 有关的式子,则有:
将 \(dp_{a_n}\) 消去,变号得:
此时我们发现 \(dp_n\) 全部消去了,变了个号后 \(f_i\) 还是从小向大转移,但是由于 \(f_i\) 的意义和 \(dp_i\) 正好相反,所以我们就成功改变了转移顺序。此时我们就已经可以正常转移了。
又注意到 \(n\) 非常大,所以我们可以考虑采用矩阵加速的方式优化转移,转移过程中以每一个 \(a_i\) 为关键转移点停止转移找到当前的 \(r\) 后再重新开始转移即可。设单次矩阵乘法的时间复杂度为 \(ω=2^3\),则时间复杂度 \(O(\omega n\log A)\)。
听说还有更高级的优化方式可以进一步优化。不过与我无关(
启发
-
【解题】如果看到一个式子,想不到什么优化方式,不妨先将这个式子向后推几步,看看能不能套用什么数学方法进行优化。
-
【赛时】仍然是,Think twice , code once.
-
【trick】找出基础状态要素之后,优化时不妨考虑将对于答案贡献相似的状态归为一类,或是省略与答案相对无关的状态,抓住影响答案贡献的关键要素设计状态,也许就会大大优化。(或者可以理解为对于每一个状态设计一个合理且有效的 hash 值进行转移,不过这种理解方式应该没有前一种更好)