数据结构——莫队算法

莫队算法是一种离线解决大量的区间查询的暴力算法。

问题引入:
  洛谷[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=lrcnt[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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值