关于区间 mex 的一个性质
前言
- 曾尝试过,没成功过,证明真的很伟大
- 真的,前所未见,甚是喜欢
序列的极小 \(mex\) 区间仅有 \(O(n)\) 级别
-
什么是极小的 \(mex\) 区间,就是从左边删除一个数 \(mex\) 值会变,从区间右边删除一个数 \(mex\) 值也会变。
-
有什么好处,若把所有区间 \([l,r]\) 视为座标 \((l,r)\) 放在平面直角座标系上,那么我们可以通过 \(O(n)\) 个极小 \(mex\) 区间的座标,构造 \(O(n)\) 个矩形,使得我们可以快速求出一个子区间的所有 \(mex\) 有关的信息。额,我好想没说明白,那算了。
-
接下来我们尝试证明有 \(O(n)\) 级别。
-
嗯,自然的我们想找一些 \(O(n)\) 级别的东西一一对应。
-
比如我说每个左端点,值对应一个极小的 \(mex\) 区间。
-
可这对吗,很快就可以找到反例 \(0,1,2,3,4,5,6\)
-
明显左右前缀都是一个合法的极小 \(mex\) 区间,这怎么半呢。
-
也许你会注意到此时我们要是说右端点值对应一 \(O(1)\) 个好像就可以。
-
此时我们考虑把所有合法的极小 \(mex\) 区间分成两组,一组满足 \(a_l<a_r\) 一组 满足 \(a_l>a_r\)
-
这里没有等于,因为等于显然不是极小的。
-
这相当于我们把全集,分成两两个子集,且子集的并一定是全集,那这两个子集要是极小 \(mex\) 区间都 是 \(O(n)\) 级别的,那全集必然也是 \(O(n)\) 级别的
-
不失一般性的我们考虑 \(a_l<a_r\) 的那一组。依据我们之前的反例,我们尝试说明右端点 \(r\) 对应的 \(l\) 只有一个。
-
若存在 \(l'<l\le r\) 显然 \(mex(l',r)\ge mex(l,r)\) ,诶我们分组了啊,所以这里只需要考虑这种情况就行啦 \(a_{l'}<a_r\),那 \(a_{l'}\) 显然不能使 \(mex\) 增加,那不就和之前的一样了吗,所以显然 \([l',r]\) 不是极小的 \(mex\) 区间。
-
这就证完了,右端点数量只有 \(n\) 个,那么依据上文所说,不存在多个 \(mex\) 有共同右端点,那一组里的数量 \(\le n\)
-
我们有两组,由此总数 \(\le 2n\)
后言
- 这,真太神奇了,我们证明一些东西,是一个级别的可以考虑,分组,然后限制就能顺理成章的沿用。
- 真的,前所未见,甚是喜欢。
怎么求呢
- 我们虽然可以这样证明,可是却不像一般的结构一样,构造证明一体化。
- 我们首先维护线段数,用来求区间 \(mex\),我们使用主席树,扫描 \(r\),主席树的下标 \(i\) 记录最大的 \(k\) 满足 \(a_k=i(k\le r)\),这样的话我们查询只需要在线段数上二分,若是左区间的最小值比我们要查询的 \(l\) 大,那么左区间的下标我们都可以取到,于是我们查询右区间,具体参考实现
namespace xds1
{
struct node { int ls, rs; int mi; }t[N * 60];
void newnode(int& x)
{
static int idx = 0; ++idx;
t[idx] = t[x]; x = idx;
}
void up(int x) { t[x].mi = min(t[t[x].ls].mi, t[t[x].rs].mi); }
void upd(int& x, int l, int r, int p, int v)
{
newnode(x);
if (l == r)return t[x].mi = v, void();
int m = l + r >> 1;
if (p <= m)upd(t[x].ls, l, m, p, v);
else upd(t[x].rs, m + 1, r, p, v); up(x);
}
int ask(int x, int l, int r, int p)
{
int m = l + r >> 1; if (l == r)return l;
if (t[t[x].ls].mi < p)return ask(t[x].ls, l, m, p);
else return ask(t[x].rs, m + 1, r, p);
}
struct xds
{
int rt;
struct xdsit
{
lr a; xds* rt;
void operator=(uint v) { upd(rt->rt, 0, n + 1, a.l, v); }
operator uint() { return ask(rt->rt, 0, n + 1, a.l); }
};
xdsit operator[](const lr& x) { return { x,this }; }
};
}
- 我们先预处理 \(mex =0,1\) 的区间。
- 我们考虑一段极小 \(mex\) 区间的特点,设区间的左右端点的值为 \(a_l,a_r\)
- 不失一般性的设 \(a_l>a_r\) 我们有 \(mex(l+1,r)=a_l\),由于 \(mex(l,r-1)=a_r\),由 \(a_l>a_r\)
- 所以 \(mex(l+1,r-1)=a_r\) 这说明啥 \([l+1,r-1]\) 中没有值等于 \(a_r\),所以必然存在一个 极小的 \(mex(l',r)=a_l(l< l')\),由于 \(mex(l+1,r)=a_l\) 所以必然存在,由于 \(a_r<a_l\) 且 \([l+1,r-1]\) 没有 \(a_r\) 所以不能减小右端点,我们只要不断减小左端点,就必然可以得到唯一一个极小的。
- 性质在于,一个极小段可以由一个极小段为左右端点,另一端点为 \(mex+1\) 构成。
- 发现了这个性质我们就可以构造了。
- 对于所有 \(mex(l,r)=v\) 的极小段,找到小于 \(l\) 的第一个 \(L\),找到大于 \(r\) 的第一个 \(R\)
- 我们吧区间 \([L,r],[l,R]\) 加到对应mex值的 vector 中。到时候去重就行了,具体参考实现
vector<node> mev[N];
namespace Mex
{
vector<int> num[N]; xds1::xds rt[N];
int mex(int l, int r) { return rt[r][l]; }
int pre(int p, int v)
{
auto r = lower_bound(num[v].begin(), num[v].end(), p);
if (r == num[v].begin())return -1; return *(--r);
}
int nxt(int p, int v)
{
auto r = upper_bound(num[v].begin(), num[v].end(), p);
if (r == num[v].end())return -1; return *r;
}
void more(int v)
{
sort(mev[v].begin(), mev[v].end());
vector<node> ans;
for (auto [l, r] : mev[v])
{
while (ans.size() && r <= ans.back().r)ans.pop_back();
ans.push_back({ l,r });
}
mev[v] = ans;
for (auto [l, r] : mev[v])
{
const int L = pre(l, v), R = nxt(r, v);
if (L != -1)mev[mex(L, r)].push_back({ L,r });
if (R != -1)mev[mex(l, R)].push_back({ l,R });
}
}
void solve()
{
for (int i = 1; i <= n; i++)
{
rt[i] = rt[i - 1]; rt[i][a[i]] = i;
num[a[i]].push_back(i);
if (a[i] != 0)mev[0].push_back({ i,i });
else mev[1].push_back({ i,i });
}
for (int i = 1; i <= n + 1; i++)more(i);
}
}

浙公网安备 33010602011771号