莫队从入门到弃坑

莫队算法是由前国家队队长莫涛提出的一种极其玄学暴力 的算法%%%,总结来说就是一个优雅的暴力双指针,配合上玄学的分块降低复杂度。

1、前置技能

1、基础的分块思想(sqrt分块)
2、重载sort排序

掌握以上两种就能解决一些简单的莫队问题,看起来很简单

3、LCA(暂时没学树上莫队,后面再补)
4、离散化(有时候需要对值域进行分块,需要离散化数值)
写这篇文章侧重总结自己练习出的问题,算法教学及复杂度证明由下面两篇大佬博客进行学习。

算法思路
复杂度证明

2、基础莫队

2.1HH的项链

传送门
Des
给你长度为n的数组,m个询问,每次询问[l, r]有多少个不同的数字
Solution
这题数据较强,正解是树状数组,但是这个题用于引入莫队十分不错,下面这个题是数据弱化版Go here
code

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n, m;
int a[N], pos[N], ans[N], cnt;
map<int, int> mp;
struct qnode
{
    int l, r, id;
} q[N];
void add(int n)
{
    if(mp[a[n]]) mp[a[n]]++;
    else
    {
        mp[a[n]] = 1;
        cnt++;
    }
}
void sub(int n)
{
    mp[a[n]]--;
    if(mp[a[n]] == 0) cnt--;
}
signed main()
{
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n;
    int siz = sqrt(n);
    for(int i = 1; i <= n; ++i)
    {
        cin >> a[i];
        pos[i] = i / siz;
    }
    cin >> m;
    for(int i = 1; i <= m; ++i)
    {
        cin >> q[i].l >> q[i].r;
        q[i].id = i;
    }
    sort(q + 1, q + 1 + m, [](qnode x, qnode y)
    {
        return pos[x.l] == pos[y.l] ? x.r < y.r : pos[x.l] < pos[y.l];
    });
    int l = 1, r = 0;
    for(int i = 1; i <= m; ++i)
    {
        while(q[i].l < l) add(--l);
        while(q[i].r > r) add(++r);
        while(q[i].l > l) sub(l++);
        while(q[i].r < r) sub(r--);
        ans[q[i].id] = cnt;
    }
    for(int i = 1; i <= m; ++i) cout << ans[i] << "\n";
    return 0;
}

2.2Rmq Problem / mex

传送门
Solution
分块后,暴力查询,如果块中有空缺,即块中有val值未被填满,即是mex。
Code

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 100;
template <typename T> inline void read(T& t)
{
    int f = 0, c = getchar();
    t = 0;
    while (!isdigit(c)) f |= c == '-', c = getchar();
    while (isdigit(c)) t = t * 10 + c - 48, c = getchar();
    if (f) t = -t;
}
int n, m, a[N], mp[N];
int val[N], cnt[N], L[N], R[N], pos[N], tot;
int ok[N];
struct qnode
{
    int l, r, id;
} q[N];
bool cmp(qnode& a, qnode& b)
{
    return (pos[a.l] == pos[b.l]) ? a.r < b.r : pos[a.l] < pos[b.l];
}
int Q()
{
    int idx = 1;
    while(val[idx] == R[idx] - L[idx] + 1) idx++;
    for(int i = L[idx]; i <= R[idx]; ++i) if(!cnt[i]) return i;
}
void add(int x)
{
    if(cnt[x] == 0) val[pos[x]]++;
    cnt[x]++;
}
void sub(int x)
{
    if(cnt[x] == 1) val[pos[x]]--;
    cnt[x]--;
}
signed main()
{
    read(n), read(m);
    for(int i = 1; i <= n; ++i)
    {
        read(a[i]);
        if(a[i] > n) a[i] = n + 1;
    }
    int siz = sqrt(n);
    for(int i = 0; i <= n; ++i) pos[i] = i / siz + 1;
    for(int i = 0; i <= n; i += siz)
    {
        L[++tot] = i, R[tot] = i + siz - 1;
    }
    for(int i = 1; i <= m; ++i)
    {
        read(q[i].l), read(q[i].r);
        q[i].id = i;
    }
    sort(q + 1, q + 1 + m, cmp);
    int l = 1, r = 0;
    for(int i = 1; i <= m; ++i)
    {
        while(l < q[i].l) sub(a[l++]);
        while(l > q[i].l) add(a[--l]);
        while(r > q[i].r) sub(a[r--]);
        while(r < q[i].r) add(a[++r]);
        ok[q[i].id] = Q();
    }
    for(int i = 1; i <= m; ++i) printf("%d\n", ok[i]);
    return 0;
}

2.3数列找不同

传送门

2.4小B的询问

传送门

3、带修莫队

3.1[国家集训队] 数颜色 / 维护队列

code

#include <bits/stdc++.h>
using namespace std;
template <typename T> inline void read(T& t)
{
    int f = 0, c = getchar();
    t = 0;
    while (!isdigit(c)) f |= c == '-', c = getchar();
    while (isdigit(c)) t = t * 10 + c - 48, c = getchar();
    if (f) t = -t;
}
const int N = 1e6 + 10;
int n, m, ans;
int a[N], ok[N], mp[N];
struct qnode
{
    int l, r, pre, id, bkl, bkr;
} q[N];
int qnum, cnum;
struct cnode
{
    int pos, val;
} c[N];
void change(int now, int i)
{
    if(c[now].pos >= q[i].l && c[now].pos <= q[i].r)
    {
        if(--mp[a[c[now].pos]] == 0) ans--;
        if(++mp[c[now].val] == 1) ans++;
    }
    swap(c[now].val, a[c[now].pos]);
}
void add(int x)
{
    if(++mp[x] == 1) ans++;
}
void sub(int x)
{
    if(--mp[x] == 0) ans--;
}
inline bool cmp(register qnode a, register qnode b)
{
    return a.bkl != b.bkl ? a.bkl < b.bkl : (a.bkr != b.bkr ? a.bkr < b.bkr : a.pre < b.pre);
}
void moqueue()
{
    int l = 1, r = 0, now = 0;
    for(int i = 1; i <= qnum; ++i)
    {
        while(l < q[i].l) sub(a[l++]);
        while(l > q[i].l) add(a[--l]);
        while(r < q[i].r) add(a[++r]);
        while(r > q[i].r) sub(a[r--]);
        while(now < q[i].pre) now++, change(now, i);
        while(now > q[i].pre) change(now, i), now--;
        ok[q[i].id] = ans;
    }
    for(int i = 1; i <= qnum; ++i) cout << ok[i] << "\n";
}
signed main()
{
    read(n), read(m);
    int siz = pow(n, 2.0 / 3.0);
    for(int i = 1; i <= n; ++i)
    {
        read(a[i]);
    }
    char op[N];
    while(m--)
    {
        scanf("%s", op);
        if(*op == 'Q')
        {
            read(q[++qnum].l);
            read(q[qnum].r);
            q[qnum].pre = cnum;
            q[qnum].id = qnum;
            q[qnum].bkl = (q[qnum].l - 1) / siz + 1;
            q[qnum].bkr = (q[qnum].r - 1) / siz + 1;
        }
        else
        {
            read(c[++cnum].pos);
            read(c[cnum].val);
        }
    }
    sort(q + 1, q + 1 + qnum, cmp);
    moqueue();
    return 0;
}

4、树上莫队

posted @ 2022-06-28 14:05  std&ice  阅读(122)  评论(0)    收藏  举报