莫队算法总结

莫队算法

例题

Given a sequence of n numbers a1, a2, …, an and a number of d-queries. A d-query is a pair (i, j) (1 ≤ i ≤ j ≤ n). For each d-query (i, j), you have to return the number of distinct elements in the subsequence ai, ai+1, …, aj.

输入

Line 1: n (1 ≤ n ≤ 30000).
Line 2: n numbers a1, a2, …, an (1 ≤ ai ≤ 106).
Line 3: q (1 ≤ q ≤ 200000), the number of d-queries.
In the next q lines, each line contains 2 numbers i, j representing a d-query (1 ≤ i ≤ j ≤ n).

输出

For each d-query (i, j), print the number of distinct elements in the subsequence ai, ai+1, …, aj in a single line.

示例

  • 输入
    5
    1 1 2 1 3
    3
    1 5
    2 4
    3 5
  • 输出
    3
    2
    3

分析

可参考博客汇总:

莫队算法的基础思想是使用两个指针,在给定的序列中进行移动,移动过程中不断更新结果,针对每一次查询,将指针移动到查询区间的两端,输出此时的结果,此结果即为该次查询得到的答案。

代码

#include <iostream>
#include <algorithm>
#include <math.h>

using namespace std;

int a[30001];
int cnt[1000001];
int Ans[500010];
int ans = 0;
int block;

inline int read()
{
    char x;
    while ((x = getchar()) > '9' || x < '0')
        ;
    int u = x - '0';
    while ((x = getchar()) <= '9' && x >= '0')
        u = (u << 3) + (u << 1) + x - '0';
    return u;
}

int buf[105];

inline void write(int i)
{
    int p = 0;
    if (i == 0)
        p++;
    else
        while (i)
        {
            buf[p++] = i % 10;
            i /= 10;
        }
    for (int j = p - 1; j >= 0; j--)
        putchar('0' + buf[j]);
}

// 定义区间,left 表示区间左端点,right 表示区间右端点
struct node
{
    int left, right, id;
} nodes[200001];

// 定义排序函数,奇偶性排序
// 输入为两各区间
bool cmp(node a, node b)
{
    // (a.left / block) ^ (b.left / block) 为判断两个区间左端点是否在同一块内,其中 ^ 为异或
    // · 如果 (a.left / block) ^ (b.left / block) 为 True,则表示区间 a 的左端点和区间 b 的左端点不在同一块
    //   那么按照左端点升序进行排序
    // · 如果 (a.left / block) ^ (b.left / block) 为 False,则表示区间 a 的左端点和区间 b 的左端点在同一块
    //   那么进行 ((a.left / block) & 1) ? a.right < b.right : a.right > b.right
    //   (a.left / block) & 1 为判断左端点属于奇数块还是偶数块,其中 & 为位与
    //   · 如果 (a.left / block) & 1 为 True(1),则表示区间 a 的左端点和区间 b 的左端点在同一奇数块
    //     那么区间 a 和区间 b 的右端点按照升序进行排序
    //   · 如果 (a.left / block) & 1 为 False(0),则表示区间 a 的左端点和区间 b 的左端点在同一偶数块
    //     那么区间 a 和区间 b 的右端点按照降序进行排序
    return (a.left / block) ^ (b.left / block) ? a.left < b.left : (((a.left / block) & 1) ? a.right < b.right : a.right > b.right);
}

// 函数功能:端点移动过程中,区间加一个元素
inline void add(int x)
{
    // a[x] 表示当前处理位置处的取值,即区间内需要增加的元素的取值
    // cnt[a[x]] 表示该取值在区间内统计出存在的个数
    // 该判断的含义为,如果当前处理位置处的取值在区间内存在个数为 0,即增加前区间内没有出现过这种取值
    if (!cnt[a[x]])
        ans++; // 那么区间内取值种类 + 1
    // 并且该种取值在区间内统计出存在的个数 + 1
    cnt[a[x]]++;
}

// 函数功能:端点移动过程中,区间减一个元素
inline void remove(int x)
{
    // 对于要去除的点 x,点 x 处该种取值在区间内统计出存在的个数 - 1
    cnt[a[x]]--;
    // 如果该种取值在区间内统计出存在的个数 - 1 后,即去除 x 处的点后
    // 该取值在区间内存在个数为 0
    if (!cnt[a[x]])
        ans--; // 那么说明去除改点后,区间内不再存在该种取值,则区间内取值种类 - 1
}

int main()
{
    int n;
    n = read();
    for (int i = 1; i <= n; ++i)
        a[i] = read();
    int q;
    q = read();
    for (int i = 1; i <= q; ++i)
    {
        nodes[i].left = read();
        nodes[i].right = read();
        nodes[i].id = i;
    }

    // 分块
    block = n / sqrt(q * 2 / 3);

    // 对块进行排序
    sort(nodes + 1, nodes + q + 1, cmp);

    // 初始化当前区间左右端点均为 0
    int l = 0, r = 0;
    for (int i = 1; i <= q; i++)
    {
        // ql 为查询区间左端点,qr 为查询区间右端点
        int ql = nodes[i].left;
        int qr = nodes[i].right;

        // 如果当前区间左端点在查询区间左端点左边
        while (l < ql)
            remove(l++); // 则当前左端点右移,左端点处移出一个点

        // 如果当前区间左端点在查询区间左端点右边
        while (l > ql)
            add(--l); // 则当前左端点左移,左端点处移进一个点

        // 如果当前区间右端点在查询区间右端点左边
        while (r < qr)
            add(++r); // 则当前右端点右移,右端点处移进一个点

        // 如果当前区间右端点在查询区间右端点右边
        while (r > qr)
            remove(r--); // 则当前右端点左移,右端点处移出一个点

        Ans[nodes[i].id] = ans;
    }

    for (int i = 1; i <= q; ++i)
        write(Ans[i]), printf("\n");

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值