莫队算法
例题
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;
}