IOI2021 Day1 简要题解
去年做 Day1 的时候睡了一觉才把 T1 做出来,所以花了大约十多个小时 ak 。
今年从七点半做到三点,也是睡了一觉才做出 T3 ,用了七个半小时。
所以有进步!
写代码的过程中可以发现我的犯错方法关于变量个数指数上升,比如在 T1 中写出 \(\text{j}\to \text{i}\) , T2 中写出 \(\text{lf[k]}\to \text{k},\text{fr}\to \text{x}\) 等优秀代码。我真是个大沙雕。
T1
完全找不出操作有什么性质。虽然可以快速定位出哪里撞了上下界,但是并没有什么用。
于是决定直接维护神奇分段函数 tag 。但是分段函数显然又和 \(c\) 有关系。
不过可以发现对于固定的 \(c\) ,tag 可以写成 \(x+t\to [l,r]\) 的形式,即先给原值加上 \(t\) ,然后卡进区间 \([l,r]\) 里。显然 \(t\) 和 \(c\) 无关,所以可以维护 \(l,r\) 关于 \(c\) 的分段函数 \(L(c),R(c)\) 。
把这两个分段函数画在同一个坐标系里,会发现对于一个前缀 \(c\le pre\) 有 \(L(c)=R(c)\) ,而对于 \(c>pre\) 则 \(L\) 是常值函数, \(R\) 是关于 \(c\) 的一次函数。
另外,两个函数的每一段都要么是常值函数,要么是斜率为 1 的一次函数。
这时候会发现两个标记虽然可以合并,但是似乎要用可持久化 fhq treap 硬搞,非常阴间。
此时想起不一定要用线段树,可以大力分块。于是其中一个标记的形式就会非常好看,合并的时候就是把另一个标记的前面一段删掉,把自己加上。
整块合并标记,零散块暴力重构即可。复杂度 \(O(q\sqrt n)\) 。
update:有简单的 \(n\log n\) 做法。
考虑单个元素,在不管上下界的情况下容易把它的取值关于时间的函数画在坐标系上。此时考虑进上下界,就是每当撞到线时把后面整体平移。
如果能找到最后一次撞到线的时刻就很容易得到最终的答案。
容易发现两次撞线之间 \(\max-\min\) 必然大于 \(c\) ,而这也是充分条件,所以可以找到这样一个后缀,后缀中就恰好包含倒数第二次和最后一次撞线的时刻。
区间操作可以差分,用线段树可以很方便地从一个元素推到下一个元素,也很容易支持二分后缀。
T2
如果正解是把所有 \(p\) 求出来,就肯定不会只求哪些最少了。所以开始想一些乱搞的方法。
显然到达关系满足传递性。
我们只需要求缩点之后没有出边的点,所以容易想到给每个点找到一个出边,只要自己还没有和指向的点缩在一起就不需要管自己。
所以维护有根内向森林结构,每次挑出一个根,给它找一个出边。如果指向另外一棵树就暂时不用管它,否则把这个环缩成一个点接着做。
问题转化为怎么找出边。我的做法是用线段树按颜色维护每个点的所有出边、以及自己拥有的钥匙的颜色。合并两点时无脑线段树合并+启发式合并即可。
复杂度应该是 \(O(n\log n)\) ?
T3
网格图是二分图,可是这有什么用呢?
把格子黑白染色,那么一条边的两边必然是一个黑一个白。
我们钦定横向边只能占用黑色格子,纵向边只能占用白色格子。那么横纵互不影响。
那么一个格子被占用两次就只有可能它四个角都有点。发现倒数第二档部分分保证没有这种情况,所以这个做法可能有希望。
如果某个黑色格子上下都有横向边,并且左边也有一条边,那么就可以把一个横向边换成右边的纵向边(而连通性不变),这个格子就活了。此时右边的格子可能又死了,所以递归?
画画图发现递归好像不会成环,那看起来挺牛逼。但是这时候想起来这个做法有一个致命错误:如果左右都没有边怎么办?
那么考虑用善良一点的顺序加边。比如从上到下从左到右加边?
加这一行之前保证所有能连通的都连通了,并且无环。
如果加的是横边:
- 如果是向下标记那就无脑加。
- 否则如果不会撞也就直接加。否则出现了四方格,那么必然已经被纵向边连通,不需要管。
如果加的是纵边:
- 如果是向右标记就无脑加。
- 否则如果没有撞那也直接加。否则出现了四方格,那么上面两个点肯定已经连通,就把自己丢到下面去。
发现做完了。