莫队算法整理

给出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('\n');
    }

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

待补充....

 

 

 

 

posted @ 2020-03-29 18:54  当然是斗笠呀  阅读(207)  评论(0编辑  收藏  举报