莫队算法是一种离线解决大量的区间查询的暴力算法。
问题引入:
洛谷[P2709] : 小B有一个序列,包含N个1~K的整数, 一共有M个询问求
∑
i
=
l
r
c
n
t
[
v
i
s
[
i
]
]
2
\displaystyle\sum_{i=l}^{r} cnt[vis[i]]^2
i=l∑rcnt[vis[i]]2,也就是区间内所有出现的数的个数的平方和。
样例输入:
6 4 3
1 3 2 1 1 3
1 4
2 6
3 5
5 6
样例输出:
6
9
5
2
我们很容易就能想到暴力算法,对于每一个询问,从左到右遍历每一个数,这样就可以求出来了。这种做法的时间复杂度是O(nm)。如果再使用map存数,那复杂度就要达到O(nm*log(n));
我们注意到问题并不强制我们在线求解,所以我们可以离线处理询问,所谓离线处理,也就是说,我们可以先将问题排个序。并且,很重要的一点是,我们可以在O(1)的时间内从[l, r]转移到[l±1,r±1]。
莫队算法核心:
我们先将这段长为n的数组分块,具体是分成
n
\sqrt{n}
n块,如图
对于两个询问
①如果它们的左端点在同一个块中,那我们就按照它们的右端点从小到大排序
②如果他们的左端点不在同一个块中,那就按他们所在的块序号从大到小排序。
这样处理之后我们所有的询问就都有序了,我们这样做的目的是为了两个不浪费上一个询问所得到的结果。
#include <iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
const int maxn = 50005;
int num[maxn];
int anss[maxn];
int cnt[maxn];
int ans;
struct node{
int l, r, id, block;
}p[maxn];
int cmp(node a, node b)
{
if(a.block==b.block)
return a.r<b.r;
return a.block<b.block;
}
void add(int pos)
{
ans-=cnt[num[pos]]*cnt[num[pos]];
cnt[num[pos]]++;
ans+=cnt[num[pos]]*cnt[num[pos]];
}
void del(int pos)
{
ans-=cnt[num[pos]]*cnt[num[pos]];
cnt[num[pos]]--;
ans+=cnt[num[pos]]*cnt[num[pos]];
}
int main()
{
int n, m, k;
scanf("%d%d%d", &n, &m, &k);
int pp = sqrt(n);
for(int i = 1; i <= n; i++)
{
scanf("%d", &num[i]);
}
for(int i = 1; i<= m; i++)
{
scanf("%d%d", &p[i].l,&p[i].r);
p[i].id = i;
p[i].block = p[i].l/pp;
}
sort(p+1,p+1+m,cmp);
int L = 1, R = 0;
ans = 0;
for(int i = 1; i <= m; i++)
{
while(p[i].r>R)add(++R);
while(p[i].r<R)del(R--);
while(p[i].l<L)add(--L);
while(p[i].l>L)del(L++);
anss[p[i].id] = ans;
}
for(int i = 1; i <= m; i++)
printf("%d\n", anss[i]);
return 0;
}