关于区间 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);
    }
}
posted @ 2025-02-23 14:26  LUHCUH  阅读(420)  评论(1)    收藏  举报