(2021.10.02)莫队学习&总结

前言

  1. 参考资料
    1. 引入&普通莫队【算法讲堂】【电子科技大学】【ACM】莫队算法

简介:优雅的暴力

  1. 莫队算法可以解决一类离线区间询问问题,适用性极为广泛。同时将其加以扩展,便能轻松处理树上路径询问以及支持修改操作。
  2. 只能应用于离线,强制在线(上一次询问的答案,作为下一次询问的内容。即这一次询问的内容,比如用到上一次询问的答案)的话就不能用莫队。
  3. 分类
    1. 普通莫队
    2. 树上莫队
    3. 带修改莫队

普通莫队

  1. 题型:能够 O ( 1 ) O(1) O(1)转移就能用普通莫队。
    在这里插入图片描述

  2. 原理:优雅暴力

    1. 怎样优化的:关键是排序。
      在这里插入图片描述

题目1:询问区间不同数的个数&普通莫队&(主席树||线段树||树状数组也都可以解决)&不要吝啬空间(所有数组都1e7+5都没问题,只要不MLE)

  1. 传送门D-query SPOJ - DQUERY
  2. 题意:给定一个长度为 n n n的数组 a a a q q q次询问,每次询问区间 [ l i , r i ] [l_i,r_i] [li,ri],要求输出区间 [ l i , r i ] [l_i,r_i] [li,ri]内不同数的个数。
    1. 1 ≤ n ≤ 3 e 4 1\le n\le 3e4 1n3e4
    2. 1 ≤ a i ≤ 1 e 6 1\le a_i\le 1e6 1ai1e6
    3. 1 ≤ q ≤ 2 e 5 1\le q\le 2e5 1q2e5
    4. 1 ≤ l i ≤ r i ≤ n 1\le l_i\le r_i\le n 1lirin
  3. 题解:查询区间 [ l , r ] [l,r] [l,r]
    1. 主席树和线段树/树状数组的题解:同一个数都只记录最后位置(标记为1),其他位置标记为0。
      1. 主席树:查询第 r r r棵线段树的区间 [ l , r ] [l,r] [l,r]的和即可。
      2. 线段树/树状数组:也是离线处理, r r r为第一关键字进行排序,一直更新到 r r r然后查询区间 [ l , r ] [l,r] [l,r]的和即可。
    2. 莫队:就没多少思维难度了。
      1. 维护任意状态 [ L , R ] [L,R] [L,R],然后暴力更新即可。
      2. 排序是关键:如果 l l l在同一块,那就按 r r r从小到大排序,否则就按 l l l从小到大排序。——复杂度分析:因为可以 O ( 1 ) O(1) O(1)更新,所以移动次数即为复杂度, l l l最多移动 n ∗ n n*\sqrt{n} nn 次, r r r最多也是移动 n ∗ n n*\sqrt{n} nn 次,所以复杂度是 O ( n ∗ n ) O(n*\sqrt{n}) O(nn )级别的。
  4. 代码
#include <bits/stdc++.h>
// #define int long long
using namespace std;
template <class T>
void read(T &x) {
    
    
    T res = 0, f = 1;
    char c = getchar();
    while (!isdigit(c)) {
    
    
        if (c == '-') f = -1;
        c = getchar();
    }
    while (isdigit(c)) res = (res << 3) + (res << 1) + (c - '0'), c = getchar();
    x = res * f;
}
const int N = 1e7 + 5;
int n, a[N], q;
int id[N], st[N], ed[N], block;
int cnt[N], nowans = 0;
int ans[N];
struct node {
    
    
    int l, r, i;
    bool operator<(const node &b) const {
    
    
        if (id[l] == id[b.l])
            return r < b.r;
        else
            return id[l] < id[b.l];
    }
} s[N];

void init_block(int n) {
    
    
    block = sqrt(n);
    for (int i = 1; i <= block; i++)
        st[i] = n / block * (i - 1) + 1, ed[i] = n / block * i;
    ed[block] = n;
    for (int i = 1; i <= block; i++)
        for (int j = st[i]; j <= ed[i]; j++) id[j] = i;
}
void update(int x, int k) {
    
    
    if (k == 1) {
    
    
        if (cnt[a[x]] == 0) nowans++;
        cnt[a[x]]++;
    } else {
    
    
        cnt[a[x]]--;
        if (cnt[a[x]] == 0) nowans--;
    }
}
signed main() {
    
    
    read(n);
    for (int i = 1; i <= n; i++) read(a[i]);
    read(q);
    for (int i = 1; i <= q; i++) read(s[i].l), read(s[i].r), s[i].i = i;
    init_block(n);
    sort(s + 1, s + 1 + q);
    // int L = 1, R = n;
    // for (int i = L; i <= R; i++) update(i, 1);
    int L = 0, R = 0;
    //虽然任意区间都ok,但是上面复杂度还会大一点->但是上面不会出现负数的情况->但是出现负数好像也没事,而且上面也是可能出现负数的
    for (int i = 1; i <= q; i++) {
    
    
        while (L < s[i].l) update(L++, -1);
        while (L > s[i].l) update(--L, 1);
        //注意加减
        while (R > s[i].r) update(R--, -1);
        while (R < s[i].r) update(++R, 1);
        ans[s[i].i] = nowans;
    }
    for (int i = 1; i <= q; i++) printf("%d\n", ans[i]);
    return 0;
}

拓展题目1:查询区间 [ l , r ] [l,r] [l,r] ∑ i = 1 i = k c [ i ] 2 \sum_{i=1}^{i=k}c[i]^2 i=1i=kc[i]2,其中 c [ i ] c[i] c[i]表示值为 i i i的个数。

  1. 传送门P2709 小B的询问
  2. 提示能O(1)转移->也是普通莫队模板题。

猜你喜欢

转载自blog.csdn.net/I_have_a_world/article/details/120583984