• 莫队学习


    莫队算法 典型的离线算法,不支持修改。不能解决强制在线的问题。莫队算法优化的核心是分块和排序。将大小为 (n) 的序列分为 (sqrt{n}) 块,从 (1)(sqrt{n}) 编号,然后根据分块结果对查询区间进行排序。一种方法是把查询区间按照左端点所在块的序号排序,如果左端点所在块相同,再按右端点排序

    复杂度分析

    莫队算法的复杂度为 (O(nsqrt{n}))

    • 排序sort对查询区间进行排序,平均复杂度为 (O(nlog n))
    • 左指针移动 设平均每个块 (i) 中分布有 (x_i) 个左端点,莫队添加、删除操作的复杂度都为 (O(1)) 。指针跨越整块的时间复杂度为 (O(sqrt{n})) ,最坏情况需要跨越 (n) 次。所以总复杂度为 (O(nsqrt{n}))
    • 右指针移动 设平均每个块 (i) 中分布有 (x_i) 个左端点,由于左端点同块的区间右端点有序,那么对于这 (x_i)个区间,右端点最坏只需总共 (O(n)) 的时间跳,即最坏需跳完整个序列。总共 (sqrt{n}) 个块,总复杂度为 (O(nsqrt{n}))
    • 总复杂度(O(nsqrt{n}) + O(nsqrt{n})+O(nlog n)=O(nsqrt{n}))

    卡常技巧

    O2优化

    #pragma GCC optimize(2) 开O2优化比不开O2优化快 (4-5) 倍甚至更多。基本可以跑 (1e6) 的数据范围。

    奇偶性排序

    将查询区间的排序函数换为如下:

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

    主要原理是右指针跳完奇数块往回跳时在同一个方向能顺路把偶数块跳完,然后跳完这个偶数块又能顺带把下一个奇数块跳完。

    移动指针的常数压缩

    指针移动的写法改为如下:

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

    题目

    #include<bits/stdc++.h>
    
    using namespace std;
    
    const int maxn = 1000005;
    const int maxq = 200005;
    int a[maxn], belong[maxn], ans[maxn], cnt[maxn];
    int n, q, ql, qr, sz, num, now;
    struct Q{
        int l ,r, id;
    }query[maxq];
    
    int cmp(Q a, Q b){
        return (belong[a.l] ^ belong[b.l]) ? belong[a.l] < belong[b.l] : ((belong[a.l] & 1) ? a.r < b.r : a.r > b.r);
    }
    int main()
    {
        scanf("%d", &n);
        for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
        sz = sqrt(n);
        num = ceil((double)n / sz);
        for(int i = 1; i <= num; i++){
            for(int j = (i - 1) * sz + 1; j <= i * sz; j++){
                belong[j] = i;
            }
        }
        scanf("%d", &q);
        for(int i = 1; i <= q; i++){
            scanf("%d%d", &query[i].l, &query[i].r);
            query[i].id = i;
        }
        sort(query + 1, query + q + 1, cmp);
        int l = 1, r = 0;
        int now = 0;
        for(int i = 1; i <= q; i++){
            ql = query[i].l;
            qr = query[i].r;
            while(l < ql) now -= !--cnt[a[l++]];
            while(l > ql) now += !cnt[a[--l]]++;
            while(r < qr) now += !cnt[a[++r]]++;
            while(r > qr) now -= !--cnt[a[r--]];
            ans[query[i].id] = now;
        }
        for(int i = 1; i <= q; i++){
            printf("%d
    ", ans[i]);
        }
        return 0;
    }
    
  • 相关阅读:
    自动化基础知识
    第一章Google软件测试介绍
    《将博客搬至CSDN》
    二叉树的先序遍历和中序遍历分析(递归)
    java 部分快捷功能
    toString
    自增自减运算符剖析
    二进制数的直接表示
    编程中的&&和||
    npm 镜像地址设置
  • 原文地址:https://www.cnblogs.com/solvit/p/11352306.html
Copyright © 2020-2023  润新知