关于区间 $mex$ 的几种做法

Posted 15owzly1-yiylcy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于区间 $mex$ 的几种做法相关的知识,希望对你有一定的参考价值。

关于区间 \(mex\) 的几种做法

题目链接

题目大意

在求 \(SG\) 函数时提到过一个 \(mex\) 函数;
\(mex(\a_i\)\) 表示在 \(a\) 中未出现的最小自然数,其中 \(a_i \in \N\)

给一个长度为 \(n\) 的序列 \(a\)\(m\) 次查询,每次查询 \(mex(\a_i\),i \in [l,r]\)

1、莫队+树状数组

权值树状数组维护每个数出现次数;
复杂度:\(O(m \sqrt n logn)\)

2、莫队+分块

把权值分块,每个块维护该块有几个数出现过;
复杂度:$O((n + m) \sqrt n);
代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
int in() 
    int x = 0; char c = getchar(); bool f = 0;
    while (c < '0' || c > '9')
        f |= c == '-', c = getchar();
    while (c >= '0' && c <= '9')
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return f ? -x : x;

template<typename T>inline void chk_min(T &_, T __)  _ = _ < __ ? _ : __; 

const int N = 2e5 + 5;

int blo, bl[N];

struct query 
    int l, r, id;
 q[N];
int n, m;
int a[N], res[N];

inline bool cmp (const query &i, const query &j) 
    if (bl[i.l] != bl[j.l])
        return bl[i.l] < bl[j.l];
    if (bl[i.l] & 1)
        return i.r < j.r;
    return i.r > j.r;


struct block_split 
    int a[N], b[1000];
    inline void modify(int p, int k) 
        if (!a[p])
            ++b[bl[p]];
        a[p] += k;
        if (!a[p])
            --b[bl[p]];
    
    int query() 
        int i;
        for (i = 1; i < bl[n]; ++i)
            if (b[i] < blo)
                break;
        for (int j = (i - 1) * blo; j <= std::min(n, i * blo - 1); ++j)
            if (!a[j])
                return j;
        return n;
    
 B;

inline void add(const int p) 
    B.modify(a[p], 1);


inline void rem(const int p) 
    B.modify(a[p], -1);


int main() 
    n = in(), m = in();
    for (int i = 1; i <= n; ++i)
        a[i] = in(), chk_min(a[i], n);
    for (int i = 1; i <= m; ++i)
        q[i] = (query)in(), in(), i;
    blo = (int)sqrt(n + 1);
    for (int i = 0; i <= n; ++i)
        bl[i] = i / blo + 1;
    std::sort(q + 1, q + 1 + m, cmp);
    for (int i = 1, l = 1, r = 0; i <= m; ++i) 
        for (; l > q[i].l; add(--l));
        for (; r < q[i].r; add(++r));
        for (; l < q[i].l; rem(l++));
        for (; r > q[i].r; rem(r--));
        res[q[i].id] = B.query();
    
    for (int i = 1; i <= m; ++i)
        printf("%d\n", res[i]);
    return 0;

3、主席树

上述做法要离线,主席树可以处理在线询问,且复杂度较优秀。
主席树维护每个数在第 \(i\) 个历史版本前,最后一次出现的位置。
对于一组查询 \(l, r\),在第 \(r\) 个历史版本中找到 最后一次出现在第 \(l\) 个历史版本前 最小的数。
代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
int in() 
    int x = 0; char c = getchar();
    while (c < '0' || c > '9')
        c = getchar();
    while (c >= '0' && c <= '9')
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return x;


const int N = 2e5 + 5;

struct persistable_tree 
    int min[20 * N], rt[20 * N], c[20 * N][2];
    int tot;

    void modify(int pos, int k, int tl, int tr, int pre, int &p) 
        p = ++tot;
        if (tl == tr)
            return (void)(min[p] = k);
        c[p][0] = c[pre][0], c[p][1] = c[pre][1];
        int mid = (tl + tr) >> 1;
        if (mid >= pos)
            modify(pos, k, tl, mid, c[pre][0], c[p][0]);
        else
            modify(pos, k, mid + 1, tr, c[pre][1], c[p][1]);
        min[p] = std::min(min[c[p][0]], min[c[p][1]]);
    

    int query(int k, int tl, int tr, int p) 
        if (tl == tr)
            return tl;
        int mid = (tl + tr) >> 1;
        if (min[c[p][0]] < k)
            return query(k, tl, mid, c[p][0]);
        else
            return query(k, mid + 1, tr, c[p][1]);
    
 T;

int main() 
    int n = in(), m = in();
    for (int i = 1, x; i <= n; ++i) 
        x = in();
        T.modify(x, i, 0, n, T.rt[i - 1], T.rt[i]);
    
    int l, r;
    while (m--) 
        l = in(), r = in();
        printf("%d\n", T.query(l, 0, n, T.rt[r]));
    
    return 0;

以上是关于关于区间 $mex$ 的几种做法的主要内容,如果未能解决你的问题,请参考以下文章

关于for循环的几种经典案例

一张图,理顺 Spring Boot应用在启动阶段执行代码的几种方式

一张图,理顺 Spring Boot应用在启动阶段执行代码的几种方式

一张图,理顺 Spring Boot应用在启动阶段执行代码的几种方式

一张图帮你记忆,Spring Boot 应用在启动阶段执行代码的几种方式

Java保留两位小数的几种做法