关于 mex
最近偶遇一堆 \(\rm mex\) 题,记一些常见结论。
- 求区间 \(\rm mex\)
首先有一个不好的莫队文明,点名批评一下,拓展性不好且难写且离线且跑的巨慢。
下面有两个求法:
- 可持久化值域线段树
其实就是主席树求法啦,但是为了区分做法 \(2\),强调值域。首先考虑转化问题,一个数 \(x\) 在区间 \([l, r]\) 中出现当且仅当 \(lst_{r, x} \ge l\)。建出值域线段树并可持久化来维护 \(lst\) 数组,于是求 \(\rm mex\) 相当于在线段树上二分。码量很小。
qwq
#define mid (l + r >> 1)
struct node{
int ls, rs, lst;
}hjt[N << 5];
int cnt, root[N];
void pushup(int o){hjt[o].lst = min(hjt[hjt[o].ls].lst, hjt[hjt[o].rs].lst);}
void add(int pre, int& o, int l, int r, int x, int k){
hjt[++cnt] = hjt[pre]; o = cnt;
if(l == r){hjt[o].lst = k; return;}
if(x <= mid) add(hjt[pre].ls, hjt[o].ls, l, mid, x, k);
else add(hjt[pre].rs, hjt[o].rs, mid + 1, r, x, k);
pushup(o);
}
int qrymin(int o, int l, int r, int pivot){
if(l == r) return l;
if(hjt[hjt[o].ls].lst < pivot) return qrymin(hjt[o].ls, l, mid, pivot);
else return qrymin(hjt[o].rs, mid + 1, r, pivot);
}
- 颜色段均摊 + 可持久化线段树
这个做法非常具有拓展性和启发性。
不妨考虑扫描线,每次把右端点 \(r\) 向右移动一段,然后使用线段树维护 \(l = 1 \dots r\) 的 \([l, r]\) 的答案。不难发现 \(\rm mex\) 具有单调性,且有很多连续段。考虑颜色段均摊,将 \(\rm mex\) 相同的区间看作一个颜色段,每次加入 \(x = a_r\) 就在线段树上二分找到 \([l_x, r_x]\) 这个 \(\rm mex = x\) 的颜色段,然后把这个颜色段 \(+1\)。 接着找到最右位置小于等于 \(r_x\) 最右的出现位置 \(p\),将颜色段分裂为 \([l_x, p], [p + 1, r]\) 两个区间。然后把左边的区间 \(\rm mex\) 更新,不断重复这个过程,直到某个数没有出现。
但是时间复杂度呢?实际上这个取决于分裂的次数,相当于新建段或者和合并段的次数。注意到,每次加入一个数,只有可能使得 \([l_x, r_x]\) 分裂出来最左边的段和前面最右的段合并,即总共只会合并 \(O(n)\) 次。最多新建段的个数也是 \(O(n)\) 的。于是时间复杂度 \(O(n \log n)\),将线段树可持久化就可以实现在线回答询问。
- 二维数点
这个相当于是上面的本质。考虑对于每个位置 \(i\) 找到上一个同色位置 \(lst_i\),在 \((lst_i, i)\) 放上一个权值为 \(a_i\) 的点,则查询区间 \([l, r]\) \(\rm mex\) 等价于数 \(x_i < l\) 且 \(y_i > r\) 的点的最小权值。二维数点即可。可以拓展到高维的 \(\rm mex\)。
- 极小 \(\rm mex\) 区间
我们定义一个区间 \([l, r]\) 是极小 \(\rm mex\) 区间,当 \(\operatorname{mex}(l, r) \ne \operatorname{mex}(l + 1, r), \operatorname{mex}(l, r) \ne \operatorname{mex}(l, r - 1)\)。
接下来证明这样的区间只会有 \(O(n)\) 个。
不妨考虑固定左端点 \(l\),寻找符合条件的 \(r\),那么不难发现,需要满足 \(\operatorname{mex}(l + 1, r) = a_l\),由于固定 \(l\) 时是单调不降的,那么满足条件的我们取最左边的一个即可。固定右端点也是一样的。因此严格上界是 \(2n\) 个。
构造方法很简单,不再写了。
例题:套娃

浙公网安备 33010602011771号