\[\large \text{Round 3 : Codeforces Round 893 (Div. 2)(VP)} \]
一言:
从你站在桥上看我的那一刻起你就是我的世界
——火影忍者
不是很满意,还是没有突破 \(\text{D}\) 题,确实是没有想到这题竟然如此毒瘤。。
\(\text{D: Trees and Segments}\)
首先不难想到一种思路,就是枚举 \(l0\),然后预处理出在得到 \(l0\) 时,能够得到最大的 \(l1\) 是多少,假设这个值为 \(d_{l0}\)。
显而易见的,\(0\) 的连续段不是在 \(1\) 连续段的右边就是在他的左边,所以考虑维护四个数组来辅助得到 \(d_{l0}\):
定义 \(l1_{i,j}\) 表示以 \(i\) 结尾,得到长度为 \(j\) 的最大 \(0\) 串需要的最少操作数。
\(l2_{i,j}\) 表示以 \(i\) 开头,得到长度为 \(j\) 的最大 \(0\) 串需要的最少操作数。
显然,对于 \(1\) 串,为了方便求出 \(d\) 数组,我们就需要换一种定义方式:
定义 \(r1_{i,j}\) 表示使用 \(j\) 次操作,得到的以 \(i\) 结尾的最大连续 \(1\) 串的长度。
\(r2_{i,j}\) 表示使用 \(j\) 次操作,得到的以 \(i\) 开头的最大连续 \(1\) 串的长度。
显然对于上述数组,我们可以通过枚举任意一个子区间 \([l,r]\),获取其变为 \(0\) 串,或者 \(1\) 串的代价,然后带回上述的几个数组中来求解。
所以有 \(d_j = \max{\ r1_{i - 1,k-l2_{i,j}},r2_{i+1,k-l1_{i,j}}}\)。
但是还有一个问题,这样求出的 \(d_i\) 两个连续 \(01\) 串根据定义是必须靠在一起的,实际上却可以是不连续的,所有我们要对 \(r1\) 求一个前缀 \(\max\),\(r2\) 求一个后缀 \(max\) 再套入刚刚 \(d_j\) 的公式。
另外,也要处理好一些边界值的细节。以及不能 \(\text{memset}\) 真的好烦啊!
\(\text{F: Rollbacks (Hard Version)}\)
看完题解之后,感觉这个题是真的妙啊。
由于这道题是 \(\text{E}\) 的加强版,所以就直接忽略这道 \(\text{E}\) 了。
首先,为了让题目更加简单,我们可以直接不考虑这个删除操作。
那么显然由于是要求不同的数字,所以实际上只有每个数出现的第一个位置是有意义的,那么我们可以考虑维护一个 \(\text{set}\) 来储存每个数出现的位置。然后还有单点修改,所以可以维护一个树状数组,来把有意义的点储存进去。
具体来说,对于插入操作,将这个数放入 \(\text{set}\),然后并根据 \(\text{set}\) 来单点修改树状数组。对于查询操作,假设当前有 \(\text{len}\) 个数,那么直接在树状数组中查询 \([l,\text{len}]\) 的有效值总和。
重点是这个删除操作,实际上我们可以直接令 \(len = len - k\),仔细想可以发现,如果我们想要撤销到这个删除,那么必然在撤销这个删除的上一个状态就是这个 \(len = len - k\) 这个状态,所以到时候直接在撤销的时候把 \(len\) 加回来即可。
至于插入操作的撤销,这个还是很简单的,就不细说了。
实际上在代码实现的时候,是还可以更简单的。
\(\text{What I learned:}\)
-
当求两个独立的区间的贡献时,可以考虑枚举两个区间的相对方位,然后通过一些预处理进行维护。
-
如果既有删除操作,有些时侯又要撤销这个删除操作,那么可以考虑在删除的时候只将数组的最后一位向前挪,并不改变实际的值。
-
维护一个序列有多少个不同的数时,可以只将每个数第一次出现的位置设为贡献 \(1\),其余位置都不存在贡献。