ZJOI2018 Day2 滚粗记 + 流水账

一脸懵逼地就被直接拉过来浙江省选了,一年参加两次省选成就达成……

讲课啥的都没听,过去休息了一天就进行比赛了。考试之前感冒没好透,精神不是 \(100\%\) 的状态,但是并无大碍(反正最后都很凉)。

这次是 8:00 到 13:00 的标准 OI 比赛时间,五个小时三道题。

题目连接应该在 loj 上可以找到:T1T2T3

首先拿到第一题,树的计数,这种复杂计数的问题对于我而言向来都是打暴力,不知为何我想了一会才往下看题……

看到第二题,是统计一个最短路算法的类似复杂度的东西(但不是复杂度,准确来说是每次更新节点的个数之和),看完题目就瞬间有了一些 idea,所以预感这题可能 AC。

然后去看第三题,一个计算几何,而且不是无脑模拟或用数据结构优化、乱搞的那种,完全不会做。(考后才知道这个是一个反演,我从来没有接触过的东西。它的变换所有点就是关于某个点朝靠近或远离的方向移动,移动后和移动前和这个点的距离互为倒数;然后那个中心点等概率出现在一个矩形区域内部,问你变换一次后的凸包点数期望)然而发现有一个测试点 \(n \le 3\),后面又保证了所有测试点有 \(n \ge 3\),这答案不就是 \(3\) 吗,直接输出拿 \(10\) 分走人。

回过去看 T2,顺着刚才闪现的灵感想了下去(在想之前我先打了一个暴力并过掉了小样例和大样例)……它求的是点权变化次数总和,那么我们可以换一种统计方式,变成统计每个点变化过多少次不就行了吗?想了想发现这样就变成水题了,因为我想的是找到每个点它最终是从左边哪个点更新过来的、从右边哪个点更新过来的,然后左右这两个点之间的点的个数就是答案……手算了一下样例发现过不了,所以感觉自己是漏掉了一些“细节”——原来我没有考虑距离问题,也就是说假设我研究的点是点 \(u\),它最终从 \(v\) 更新过来的,那么 \(u\)\(v\) 之间的边的数量需要向另一个方向倍长,然后得到的这个区间内的点数才是答案……然后自以为这个肯定是正确结论了,于是想了半天如何处理这个问题,由于我研究的点是一段一段的,不能暴力研究每个点(否则复杂度退化成每次询问都要 \(O(n)\)),以为用主席树啥的暴力搞;后来不知什么时候突然发现这个结论的严重 bug,其实应该是左边、右边单调栈各一段,按照和点 \(u\) 的距离合并的单调栈大小(就是说这个区间内不一定所有点都会更新到 \(u\),只有往两边不断变小才会更新);那我想单调栈合并不可能快速进行啊,感觉非常不对劲,于是打算推翻之前的想法,换另一个思路。

其实统计变化次数不一定要统计每个城市变化次数,可以统计每个给出的关键点的影响范围大小,这个影响范围大小的总和也是答案。然后一眼看出这个“影响范围”就是一个区间,所以我们需要二分来确定两个端点的位置。我想一个关键点是有可能“吃掉”另一个关键点的,\(a\) 吃掉 \(b\) 意味着最终最短路不会从 \(b\) 出发,那么这时 \(a\) 的影响范围完全包含 \(b\)。(以下只讲求右端点)所以我就先二分这个 \(a\) 会吃掉哪些关键点,然后影响范围一定在最远的被吃掉的关键点的右边,那么这个时候再二分一下不就行了吗?我于是立刻敲起了代码,调了一会过掉了小样例,接下来就去测大样例,发现似乎只有第一个询问对了;我重新想了一下这个思路,感觉没啥漏洞,没办法只好和暴力用小一点的数据对拍。

没想到随便生成一组数据就拍出错了,于是我就看这个数据,发现了我刚才思路的漏洞:限制我影响区间不能往后扩的的不只有第一个没被吃掉的关键点,后面的关键点可能会有更强的限制……于是我就想,最强的限制不就是 \(v_i + S_{i-1}\)\(v_i\) 就是关键点的初始值,\(S_i\)\(w_i\) 的前缀和)最小的关键点吗?直接用它限制就好了。改了一下代码,再拍,还有错,但错误明显变少了。接着找问题,这个想法还是有漏洞:注意到假设关键点 \(b\) 限制了关键点 \(a\),那么我是要二分到那个从 \(a\) 更新比从 \(b\) 更新更小的最靠右的点 \(x\),这个 \(x\) 就是 \(b\)\(a\) 的限制,但如果这个 \(x\)\(a\)\(b\) 的中点(这个中点是假设边权都为 \(1\) 的中点,就是只数边的条数)左边,这个 \(b\)\(a\) 就没有任何限制了(或者应该说限制不是 \(x\) 而是 \(a\)\(b\) 的中点),所以我们要排除掉这些没有限制的点。于是我想我需要处理每个点右边离它最近的且权值 \(v_j - S_{j-1}\) 比他小的点,这样维护一棵树,那么我们就可以在某个点到根的链上进行二分了(为什么可以二分呢?那是因为随着往根上走,中点会越来越靠右,限制点会越来越靠左,所以是单调的,我们可以二分到“限制有效”的那个点),于是搞个树上倍增就可以 \(O(\log^2 n)\) 解决了。

想到这里,发现已经 9:15 了,T1 暴力还没打呢,这个正解代码也不知道写不写得完了——又是一个艰难的选择。这里我选择了求稳,我去打了 T1 的暴力(原因是 T1 暴力看上去非常好写;而且当时我想 T2 时间太久我不确定我是否还足够理智,不确定最终的那个想法是否还有漏洞,如果有,那我正常比赛就完全挂掉了,连暴力分都没拿满,不值得用整场比赛赌一个不知道是否正确的正解),打完暴力只剩 \(10\) 分钟了,我把 T2 那个写了一半的“正解”和暴力拼了起来,过掉了小样例、大样例,然后查一下三道题的目录、文件读写等等就结束了。

考后发现 T2 正解非常简单,根本不需要先二分“吃掉”哪些关键点,直接二分求区间端点就好了,程序实现、思路上都会简洁许多。最后在 loj 上过掉了。考场上最后想出的做法没有实际验证,但是从思路上看没有漏洞了。


总结一下,这次比赛思路上走的弯路非常多,T2 我想错了至少 \(4\) 次,每次都还觉得挺对,没有及时意识到错误直接开始考虑程序怎么写,导致过量时间的浪费,造成一个小小的遗憾(如果 A 掉 T2 应该就全场 rank 1 了)……并且想错是一件很可怕的事情,最后我的那个正解想先二分被“吃掉”的关键点受的就是之前“考虑求每个点被改多少次”的思路的影响,拐了很多弯,没有找到最直接、最有效、最简洁、最优美的做法;其实我觉得即便考场上我调出来了 T2,用的如果是那个复杂的做法的话也是值得反思的。这次我想到了正解,说明水平也没那么差劲,但这次想到的正解也反应出来我的思维水平还没有达到一定的高度,我在想错之后没能够完全跳出错误的圈子,而是多少仍然受着影响……想错可能无法避免,所以我们在努力尝试想对的同时似乎也应该准备一下想错后的“应急方案”。

这场比赛也有不错的几点:

  • 首先我找准了场上最能 AC 的一道题,并且不管怎么样还是想出来了。
  • 我能及时放弃,做出正确的选择(指的是放弃 T2 写不完的正解,选择打 T1 暴力),我认为考场上求稳比赌命要好很多。
  • 在最后的时刻打 T1 暴力我能做到很快打对,说明比之前冷静,考试心态调整得不错。

最后祝愿自己在之后的比赛中稳定发挥,拿到有能力拿的分,写完能想到的 idea。

posted @ 2018-05-02 11:00  xjr01  阅读(251)  评论(0编辑  收藏  举报