Data Structures 10.23~10.26
[CEOI 2025] Equal Mex
给定序列 \(a\),有 \(q\) 次询问每次给出 \(l, r\),求 \(k\) 的个数,满足可以将 \(a[l ,r]\) 划分成 \(k\) 个子段,每个子段的 MEX 相等。
\(1 \le n, q \le 6 \times 10^5\)
看完题之后想到性质:这些子段的 MEX 应该都等于 \(a[l, r]\) 的 MEX 值 \(b\)。因为首先不可能所有子数组的 MEX 均 \(<b\),因为存在至少一个子数组中有数 \(b\);然后 MEX 不可能 \(>b\),否则 \(b\) 必须要出现在 \(a[l, r]\) 中,与 MEX 定义矛盾。
顺着这个思路继续推,你发现 \(k\) 越小一定越能划分。好的,问题变为求最大的 \(k\)。
有结论:极小 MEX 区间个数为 \(\mathcal O(n)\),因为每个左端点至多对应一个区间,每个右端点至多对应一个区间。
我们发现询问实际上是求选出区间内不交且 \(=\) 区间 MEX 的子区间个数最大值,按区间 MEX 值分组,对于每组直接用倍增 + 扫描线即可。
这里再记录一下怎么求出所有极小 MEX 区间:
回忆求区间 MEX 的过程,扫右端点,维护每个数 \(i\) 上一次出现位置 \(\mathrm{last}_{i}\),用值域线段树维护区间 \([0, k]\) 的 \(\mathrm{last}_i\) 最小值,求 \(l\) 的答案直接用线段树上二分即可。
求 MEX 区间同理,枚举右端点 \(i\),考虑一个 MEX 区间是极小的,则左右两端点向内移动都会改变 MEX 大小。首先考虑左端点移动限制,固定了右端点后找到所有改变 MEX 的左端点位置,作为候选 \(j\)。
然后考虑右端点限制,这是简单的,只需要满足 \(j > \mathrm{last}_{a_i}\) 就行了。
TEST_90
给定序列 \(a\),\(q\) 次询问给定 \([l, r]\) 求有多少子区间 \([i, j]\) 满足区间中出现的数的个数为奇数。
\(1 \le n \le 10^6, 1 \le m \le 10^6, 1 \le a_i \le n\)。
按照 \(r\) 扫描线,你能够动态维护哪些 \(l\) 满足区间中出现的数的个数为奇数,所以考虑历史和。
对于线段树上每个节点(管辖区间 \(P\))维护 \(d = (S, L, H) \in D\) 分别表示:
- \(S\):\(l \in P\) 中,\([l, r]\) 出现数的个数为奇数的 \(l\) 数量
- \(L\):\(P\) 的长度,可以通过这个算出 \([l, r]\) 出现数的个数为偶数的 \(l\) 数量 \(L-S\)。
- \(H\):\(S\) 历史和。
考虑我们对于 \(S\) 的标记是 \(0/1\) 的,使得 \(S \gets L - S\),\(L\) 不变。标记维护 \(t = (\alpha, \beta, \gamma) \in T\),\(D * T \to D\) 定义为:
至于 \(T*T \to T\) 怎么推,有普遍方法:由结合律,\(D*(T*T) = (D*T)*T\),先算出 \(D*T\) 再进一步算出 \(D * T * T\) 对应系数即可。
简单推得,若 \(f = (\alpha, \beta, \gamma), g = (\alpha',\beta',\gamma')\):
注意 \(\alpha\) 是布尔值所以能这么化。
所有操作形式均可化为标记:幺元 \((0, 0, 0)\),区间翻转 \((1, 0, 0)\),记录历史和 \((0, 1, 0)\)。
用线段树简单维护即可。
Yuno loves sqrt technology II
给定序列 \(a\),每次查询区间 \([l, r]\) 的逆序对数。
\(n, m \le 10^5, 1 \le a_i \le 10^9\)
二次离线板子。二次离线的想法是:莫队的移动对于答案的修改是单点对于一个区间的询问,共 \(\mathcal O(n \sqrt m)\) 次;而我们扫描时,对于区间却只需要 \(\mathcal O(n)\) 次插入,这很不平衡。解决方案是,把这些单点对于区间的询问离线下来再做一次扫描线,用分块维护 \(\mathcal O(\sqrt n)\) 区间修改,\(\mathcal O(1)\) 单点询问,将复杂度降低。
本题中要求区间逆序对数,莫队把它变成 \(\mathcal O(n \sqrt m)\) 次查询 \([l, r]\) 中选数,与 \(a_i\) 产生了多少个逆序对(其中 \(i = l-1\) 或 \(i = r + 1\)),不妨以 \(i = r + 1\) 为例,查询 \(j \in [l, r]\) 中有多少 \(a_j > a_i\),可以离线下来做扫描线。
具体地,我们把它差分成 \([1, l-1]\) 与 \(a_i\) 的询问和 \([1, r]\) 与 \(a_i\) 的询问,于是每个询问形如 \((j, i)\),求前缀 \([1, j]\) 和中有多少 \(> a_i\) 的。用一个简单的扫描线即可求,但是注意到插入仅有 \(\mathcal O(n)\) 次,查询却有 \(\mathcal O(n\sqrt m)\) 次,用简单的分块即可平衡这个问题,将插入变成区间修改 \(\mathcal O(\sqrt n)\),查询变成 \(\mathcal O(1)\) 单点查即可,总复杂度 \(\mathcal O(n \sqrt m + n\sqrt n)\)。
另外有一类是上次模拟赛题,启发式合并中的询问也是类似的,用 dfn 序后,变成 \(\mathcal O(n\log n)\) 次单点对区间的询问。但这一类问题 \(\mathcal O(n \log n)\) 与 \(\mathcal O(n)\) 差异不太大,所以优化效果不太好。可能最多去掉一个 \(\sqrt {\log n}\)。
[Ynoi2079] r2pspc
给定序列 \(a\),每次查询 \(\mathrm{popcount}\left(\sum\limits_{i = l}^r 2^{a_i}\right)\)。
\(1 \le n \le 10^5, 1 \le m \le 10^6\)。
考察二进制加法进位操作,定义势能为 \(1\) 的个数,每次操作最多增加 \(1\) 的势能,进位消耗势能,复杂度均摊 \(\mathcal O(l)\),其中 \(l\) 为区间长度。所以暴力每次直接遍历整个区间,复杂度均摊 \(\mathcal O(nm)\)。如果引入删除操作,则复杂度无法保证,因为一次删除会使势能不可控地增加:\(1000000\) 删去 \(1\),变成 \(0111111\)。
通过均摊的观察,值域可以离散化到 \(\mathcal O(n)\)。
要去除删除操作,考虑回滚莫队。然而回滚莫队有撤销操作,对于均摊来讲仍然不友好。右端点的移动均摊 \(\mathcal O(1)\) 是对的,难点在于:若干次撤销后左端点的移动并不是均摊的。
我首先的想法是:影响均摊的就是移动右端点时留下的一堆 \(1\),考虑在进位时跳过这一部分的 \(1\),其余直接暴力做就对了。怎么跳过?非常简单,考虑在右端点移动时维护值域上的 \(1\) 连续段,在左端点加入时发现 \(a_l\) 已经是处于之前某个 \(1\) 连续段 \([L, R]\) 中就截断这个连续段,将连续段的右端点设为 \(a_l - 1\),并将维护的值数组的第 \(R+1\) 位加一继续做进位。
问题是,查找处于哪个连续段的代价并不便宜,非均摊的并查集复杂度最好只能做到 \(\mathcal O(n \sqrt q\log n)\) 😭。
然而观察题解或者细心一点会发现,我们没必要每次都精确维护好 \(\sum\) 的二进制形式,因为查询仅有 \(\mathcal O(m)\) 次。考虑在查询时 \(\mathcal O(\sqrt n)\) 还原 \(\sum\) 即可统计 popcount。
这部分相当精妙(榨干了❤️每一个可以平衡的地方),首先将值域按照莫队块中的出现的 \(a_l\) 划分一下,变成 \([1, v_1],(v_1, v_2],(v_2, v_3],\dots, (v_{B}, n]\)。好处是什么,每次插入的值都是在值域块末,最多影响下一个块前 \(\log n\) 个位置。
于是可以只维护每个块前 \(\log n\) 个位置,用 bitmask。然后用 \(p_i\) 表示如果使块 \(i\) 的第 \(\log (n) + 1\) 位加一,进位会进到哪里(容易发现最多进一次位),另记 \(q_i\) 表示从前一个块中得到的进位数,有 \(q_i \le n\),为了计数,还需维护 \(cnt_i\) 表示不考虑进位及前 \(\log n\) 位的 \(1\),高位共有多少个 \(1\)。右端点移动时可以暴力修改,均摊复杂度 \(\mathcal O(1)\),左端点由于一定处在块末,所以只需要考虑是否进位即可。
查询时直接遍历值域块即可。撤销是简单的,总复杂度 \(\mathcal O(n\sqrt m)\)。块长取 \(\frac{n}{\sqrt m} = 100\),实际还需平衡常数。
[Ynoi2011] 初始化
维护序列 \(a\),有 \(m\) 次操作:修改给定 \(x, y, z\),将 \(a_{y+kx}(k \ge 0)\) 增加 \(z\);询问给定 \(l, r\),求区间和。
\(1 \le n, m \le 2 \times 10^5\)。有 \(1 \le y \le x \le n\)。
很无聊。
根号分治,若 \(x > B\) 则直接暴力增加,用序列分块 \(\mathcal O(1) - \mathcal O(\sqrt n)\) 维护和,复杂度为 \(\mathcal O(\frac{n}{B})\);对于每个 \(x \le B\) 开一个数组维护每个位置的增加量,询问时遍历所有 \(x \le B\) 计算区间和。
考虑 \(x \le B\) 部分:
- 修改时,\(x\) 固定,考虑用一个 \(t[x][y]\) 记录维护余数类的区间增加。
- 询问时,枚举所有 \(x\),注意到询问所需的余数类 \(y\) 是连续的,在修改时维护一个 \(t[x][y]\) 的前缀和即可,总 \(\mathcal O(B)\)。
取 \(B = \sqrt n\),复杂度为 \(\mathcal O(m \sqrt n)\),空间复杂度 \(\mathcal O(n + m)\)。
[JRKSJ R2] 你的名字。
给定序列 \(a\),每次查询给出 \(l, r, k\),求区间 \([l, r]\) 在模 \(k\) 意义下的最小值。
\(1 \le n \le m \le 3 \cdot 10^5, 1 \le a_i,k \le 10^5\)。
下面视 \(m=\Theta(n)\),令 \(v = \max a_i\)。
考虑根号分治,对于 \(k \le B\) 的直接预处理,用序列分块做,单次询问时间复杂度 \(\mathcal O(\sqrt n)\),空间复杂度为 \(\mathcal O(B\sqrt n)\)。
对于 \(k > B\),好妙啊,稍微放宽一点,抓住如下性质:
先枚举 \(j\),复杂度 \(\mathcal O(\frac{v}{B})\),则问题变为区间中 \(\ge jk\) 的最小值。将所有 \(jk\) 拿出来排序,共 \(\mathcal O(\frac{nv}B)\) 个。我们要做的是 \(\mathcal O(n)\) 次单点修改,\(\mathcal O(\frac{nv}{B})\) 次查询当前区间的最小值。考虑序列分块,块内维护前后缀 min(同块直接暴力即可,不根号分治),块间用 ST 表,单点修改最多改变 ST 表中 \(\sum _{2^i \le \sqrt n} 2^i = \Theta(\sqrt n)\) 个位置。查询 \(\mathcal O(1)\),修改 \(\mathcal O(\sqrt n)\),总复杂度当 \(B = \sqrt v\) 时取到最小值 \(\mathcal O(n(\sqrt n + \sqrt v))\)。
实现非常简单。

浙公网安备 33010602011771号