初级莫队算法的详解(附加一个例题)

119 篇文章 1 订阅
113 篇文章 1 订阅
例题链接:D-query

题目大意:给你n(1 ≤ n ≤ 30000)个数(1 ≤ ai ≤ 10^6),q(1 ≤ q ≤ 200000)个询问,每个询问有l,r两个数,问这个区间内有多少个不同的数。

Input
5
1 1 2 1 3
3
1 5
2 4
3 5

Output
3
2
3 

最暴力的方法就是,先从【1,5】区间一个一个查询,在从【2,4】区间一个一个查询……
但是这样可能会出现n^2的时间复杂度,所以这样是肯定不行的。

莫队核心思想(包括带修莫队也是这样,只不过是加入和修改的部分而已)
我们可以先查找【1,5】区间,记录每一个元素的个数,再拓展到【2,5】区间,这样是只是少了1号元素,再拓展到【2,4】区间,少了5号元素,所以【2,4】的答案就出来了,以此类推可以求到所有的答案。

不过这样是肯定不行的,因为区间的顺序不同所消耗的时间是不同的,也有可能会超时。
此时我们要对所有的区间进行排序,使得所需要查询的次数降低,以此来降低时间复杂度。

不知道我们该如何排序呢?
我们要利用分块的思想(只是不清楚为啥要这样,可以降低时间复杂度),可以将时间复杂度降到O(n√n)。
我们的排序是按照左段点所在的块为第一关键字,以右端点为第二关键字(这点很重要)。

排完序后从左往右处理询问(离线),过程就是上述的核心思想部分。

基本上莫队的做法就是上述部分,只不过不同的题目略有不同而已。

下面是每一个分部:

  block=sqrt(n*1.0);

1.块的大小:一般是sqrt(n),n为数组长度。

bool cmp(node f1,node f2)
{
    if(f1.blk==f2.blk)//左段点所在的块
        return f1.t<f2.t;
    return f1.s<f2.s;
}

2.对查询的排序
3.如何通过加1或-1,从上一个区间变化到本区间。

下面是详细代码:

#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
using namespace std;

#define mem(a,b) memset(a,b,sizeof(a))
const int maxn=30000+10;
const int max1=1e6+9;
typedef long long ll;

int a[maxn];
int inq[max1],sum[maxn*10];
int block,ans;

struct node
{
    int s,t;//区间范围
    int id;//查询的编号
    int blk;//左端点所在的块
} edge[maxn*10];

bool cmp(node f1,node f2)//排序
{
    if(f1.blk==f2.blk)
        return f1.t<f2.t;
    return f1.s<f2.s;
}

void init()
{
    mem(inq,0);
    ans=0;
}
//每道不同的题目思想不同,下面两个函数的内容也各不相同
void up(int x)//扩充区间
{
    if(inq[a[x]]==0)
        ans++;
    inq[a[x]]++;
}
void down(int x)//缩小区间
{
    inq[a[x]]--;
    if(inq[a[x]]==0)
        ans--;
}

int main()
{
    int n,m;
    while(~scanf("%d",&n))
    {
        init();
        block=sqrt(n*1.0);//块的大小
        for(int i=1; i<=n; i++)
            scanf("%d",&a[i]);
        scanf("%d",&m);

        for(int i=0; i<m; i++)//离线记录
        {
            scanf("%d%d",&edge[i].s,&edge[i].t);
            edge[i].id=i;
            edge[i].blk=edge[i].s/block;
        }
        sort(edge,edge+m,cmp);

        int l=1;//初始左右区间
        int r=0;
        ans=0;//答案
        for(int i=0; i<m; i++)
        {
            int s=edge[i].s,t=edge[i].t;
            while(r<t)//区间扩充
            {
                r++;
                up(r);
            }
            while(l>s)//区间扩充
            {
                l--;
                up(l);
            }
            while(r>t)//区间缩小
            {
                down(r);
                r--;
            }
            while(l<s)//区间缩小
            {
                down(l);
                l++;
            }
            sum[edge[i].id]=ans;//记录答案
        }
        for(int i=0; i<m; i++)
            printf("%d\n",sum[i]);
    }
    return 0;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值