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序应用),和路径统计(欧拉序应用))、回滚莫队

    待补充....

     

     

     

  • 相关阅读:
    idea设置全局ignore
    win 2012 安装mysql 5.7.20 及报错 This application requires Visual Studio 2013 Redistributable. Please ins
    win 2012 安装mysql 5.7.20 及报错 This application requires Visual Studio 2013 Redistr
    kafka 删除 topic
    java编译中出现了Exception in thread “main" java.lang.UnsupportedClassVersionError
    Centos中使用yum安装java时,没有jps的问题的解决
    Spring 整合Junit
    Spring纯注解配置
    Spring 基于注解的 IOC 配置
    打印java系统的信息
  • 原文地址:https://www.cnblogs.com/DreamACMer/p/12594018.html
Copyright © 2011-2022 走看看