zoukankan      html  css  js  c++  java
  • 莫队算法整理

    给出n个数,给出m个询问,每个询问要求输出区间[l,r]之间的数字的种类。(1 <= n , m , ai <= 1e5)

    如果题目不要求强制在线的话,这样的题目可以考虑使用莫队算法。(注意数字的取值范围,如果数字是[1,1e9]那就不可以了)

    首先拿到这道题目可以想到的最暴力的解法,遍历所有区间[l,r],用cnt数组记录每个数组出现的次数,最后统计出现数字的种类,这样的时间复杂度就是O(m * (n + MAXai)),肯定是过不了题的。

    然后莫队算法这种通过优美优化之后的暴力算法就能解决这种问题,莫队算法就是通过分块思想,将查询区间进行排序,将时间复杂度优化到O(n * sqrt(n))

    既然需要排序,那么需要了解莫队是怎么排序的:

      首先将整个序列分为sqrt(n)块,每块长度为sqrt(n),并按照1到sqrt(n)进行标号。这里假设len = sqrt(n)。然后向询问的区间进行排序,以区间左端点所在的块的序号进行排序,如果所在块的需要相同,则再按照右端点从小到大进行排序。

      然后分析一下所需要的时间复杂度:

      (1)sort的时间复杂度:n * log(n)

      (2)因为区间左端点是按照块的序号进行排序的,而块内的左端点实际上是无序的,这里考虑他的最大时间复杂度,即每一次移动都从块的一端移动到另一端,那么m个端点所需要的复杂度就是O(m * len)。

      (3)在区间左端点处于同一块的时候,区间的右端点是有序的,同样考虑他的最大时间复杂度,即每一次移动都从序列的最左端移动到序列的最右端,因为一共有len个块,所需要的复杂度就是O(len * n)

      这样一来总的时间复杂度就优化到了O(n * log(n) + m * sqrt(n) + n * sqrt(n)) = O(n * sqrt(n))

    标准的排序代码

    int cmp(node& a, node& b)
    {
        return (pos[a.l] ^ pos[b.l]) ? pos[a.l] < pos[b.l] : a.r < b.r;
    }

    但是当实际运用莫队算法的时候,因为莫队算法的复杂度本身就是O(n * sqrt(n))在一些情况下,极其容易被卡常数。

    这里就需要许多优(shen)美(xian)的优化

    1.莫队奇偶性排序

    (这个排序真的是优(shen)美(xian))

    最最最最重要的优化!!!

    看似没有用,但是对于每个块的查询都可以减少很多时间

    int cmp(node& a, node& b)
    {
        return (pos[a.l] ^ pos[b.l]) ? pos[a.l] < pos[b.l] : ((pos[a.l] & 1) ? a.r < b.r : a.r > b.r);
    }

    对于奇数块右端点从小到大排序,偶数块右端点从大到小排序。

    原理其实很简单:右指针跳完奇数块,往回跳的时候,顺便把偶数块跳完,然后再跳下一块奇数块

    2.移动指针的压缩

    就是把这部分

    void add(int pos) {
        if(!cnt[aa[pos]]) ++now;
        ++cnt[aa[pos]];
    }
    void del(int pos) {
        --cnt[aa[pos]];
        if(!cnt[aa[pos]]) --now;
    }

    和这部分

    while(l < ql) del(l++);
    while(l > ql) add(--l);
    while(r < qr) add(++r);
    while(r > qr) del(r--);

    根据运算符的优先级生生压缩成

    while(l < ql) now -= !--cnt[aa[l++]];
    while(l > ql) now += !cnt[aa[--l]]++;
    while(r < qr) now += !cnt[aa[++r]]++;
    while(r > qr) now -= !--cnt[aa[r--]];

    (垃圾函数调用,我选择inline)

    3.究极开O2 #pragma GCC optimize(2)

    如果实在被卡到落泪,然后又写不出其他算法可以一试(当然如果比赛已经把这个禁了,那就不要增加罚时了)

    这里给出一些简单的例题

    1.SP3267 DQUERY - D-query

    https://www.luogu.com.cn/problem/SP3267

    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    #define isdigit(x) ((x) >= '0' && (x) <= '9')
    #define maxn 1000010
    using namespace std;
    
    
    
    int read()
    {
        int X = 0, w = 0; char ch = 0;
        while (!isdigit(ch)) { w |= ch == '-'; ch = getchar(); }
        while (isdigit(ch)) X = (X << 3) + (X << 1) + (ch ^ 48), ch = getchar();
        return w ? -X : X;
    }
    
    int n, m;
    int arr[maxn], cnt[maxn], res = 0 , pos[maxn] , ans[maxn];
    int size, bnum;
    struct node
    {
        int l, r ,id;
    }q[maxn];
    
    
    int cmp1(node & a, node & b) {
        return (pos[a.l] ^ pos[b.l]) ? pos[a.l] < pos[b.l] : ((pos[a.l] & 1) ? a.r < b.r : a.r > b.r);
    }
    
    void add(int x) {
        if (!cnt[arr[x]]) ++res;
        ++cnt[arr[x]];
    }
    
    void del(int x)
    {
        --cnt[arr[x]];
        if (!cnt[arr[x]]) --res;
    }
    void printi(int x) {
        if(x / 10) printi(x / 10);
        putchar(x % 10 + '0');
    }
    
    int main()
    {
        scanf("%d", &n);
        int size = sqrt(n);
        int bnum = ceil((double)n / size);
        for (int i = 1; i <= n; ++i)
            pos[i] = (i - 1) / sqrt(n) + 1;
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &arr[i]);
        }
        m = read();
        for (int i = 1; i <= m; ++i)
        {
            scanf("%d %d", &q[i].l, &q[i].r);
            q[i].id = i;
        }
        sort(q + 1, q + 1 + m, cmp1);
        int l = 1, r = 0;
        for (int i = 1; i <= m; ++i)
        {
            int qr, ql;
            ql = q[i].l, qr = q[i].r;
            while (l < ql) del(l++);
            while (l > ql) add(--l);
            while (r > qr) del(r--);
            while (r < qr) add(++r);
            ans[q[i].id] = res;
        }
        for (int i = 1; i <= m; ++i)
        {
            printi(ans[i]), putchar('
    ');
        }
    
        return 0;
    }
    AC代码

    建议手动交C++11,被CE到哭

    2.P2709 小B的询问

    https://www.luogu.com.cn/problem/P2709

    #include<cstdio>
    #include<string.h>
    #include<algorithm>
    #include<cmath>
    #include<iostream>
    #include<vector>
    #include<queue>
    #include<set>
    #include<map>
    #include<cctype>
    #define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
    #define mem(a,x) memset(a,x,sizeof(a))
    #define lson rt<<1,l,mid
    #define rson rt<<1|1,mid + 1,r
    #define P pair<int,int>
    #define ull unsigned long long
    using namespace std;
    typedef long long ll;
    const int maxn = 5e5 + 10;
    const ll mod = 998244353;
    const int inf = 0x3f3f3f3f;
    const long long INF = 0x3f3f3f3f3f3f3f3f;
    const double eps = 1e-7;
    
    
    inline ll read()
    {
        ll X = 0, w = 0; char ch = 0;
        while (!isdigit(ch)) { w |= ch == '-'; ch = getchar(); }
        while (isdigit(ch)) X = (X << 3) + (X << 1) + (ch ^ 48), ch = getchar();
        return w ? -X : X;
    }
    
    
    ll n, m, k;
    ll arr[maxn], pos[maxn], res = 0, cnt[maxn] = {0}, ans[maxn];
    struct node
    {
        int l, r, id;
    }q[maxn];
    
    
    int cmp(node& a, node& b)
    {
        return (pos[a.l] ^ pos[b.l]) ? pos[a.l] < pos[b.l] : ((pos[a.l] & 1) ? a.r < b.r : a.r > b.r);
    }
    
    void add(int x) 
    {
    
        ++cnt[arr[x]];
        res += (cnt[arr[x]] * cnt[arr[x]] - (cnt[arr[x]] - 1) * (cnt[arr[x]] - 1));
    }
    void del(int x)
    {
        --cnt[arr[x]];
        res -= ((cnt[arr[x]] + 1) * (cnt[arr[x]] + 1) - cnt[arr[x]] * cnt[arr[x]]);    
    }
    
    int main()
    {
        n = read(), m = read(), k = read();
        for (int i = 1; i <= n; ++i)
        {
            pos[i] = (i - 1) / sqrt(n) + 1;
        }
        
        for (int i = 1; i <= n; ++i)
        {
            arr[i] = read();
        }
        
        for (int i = 1; i <= m; ++i)
        {
            q[i].l = read(), q[i].r = read();
            q[i].id = i;
        }
        //cout << "debug" << endl;
        sort(q + 1, q + 1 + m, cmp);
        int l = 1, r = 0;
        for (int i = 1; i <= m; ++i)
        {
            int ql = q[i].l, qr = q[i].r;
            //cout << ql << " " << qr << endl;
            while (l < ql) del(l++);
            //cout << res << endl;
            while (l > ql) add(--l);
            //cout << res << endl;
            while (r > qr) del(r--);
            //cout << res << endl;
            while (r < qr) add(++r);
            //cout << res << endl;
            ans[q[i].id] = res;
        }
        for (int i = 1; i <= m; ++i)
            cout << ans[i] << endl;
        return 0;
    }
    AC代码

    3.P3709 大爷的字符串题

    https://www.luogu.com.cn/problem/P3709

    4.P4074 [WC2013]糖果公园

    https://www.luogu.com.cn/problem/P4074

    5.P1903 [国家集训队]数颜色 / 维护队列(带修改的莫队)

    https://www.luogu.com.cn/problem/P1903

    莫队扩展——树上莫队(子树统计(dfs序应用),和路径统计(欧拉序应用))、回滚莫队

    待补充....

     

     

     

  • 相关阅读:
    垃圾处理现状
    买了个学生阿里云114元啊,安装mysql
    4 存储器
    3 总线
    崔大哥说基础很重要
    idea使用小积累mac
    为啥要用left join on where这样的东西
    观察者模式
    从shell中退出python命令
    locust性能测试入门
  • 原文地址:https://www.cnblogs.com/DreamACMer/p/12594018.html
Copyright © 2011-2022 走看看