莫队算法

莫队算法主要解决离线 区间 查询数量巨大,查询序列和查询数均为10的5次方左右,但要保证l到l+1和l到l-1,r也同理对结果的影响要能O(1)的计算出来。据说适用于大多数的大数量的区间的查询题(线段树不熟练的推荐学习此算法)

数组一般从1开始读入,左指针L=1,又指针R=0;

莫队算法的关键在于

(1)块的大小,下面那道题块的大小900就过不了,1000就过了,设给定序列长度为n一般块的大小为sqrt(n);

(2)增加和删除函数,如何将l到l+1和l到l-1,r也同理对结果的影响O(1)计算出来

(3)移动的顺序是先移动r还是l,最好是先r,因为先移动l会造成负数,无法判断

以下是复杂度证明,部分摘自其他博客

设分块大小为√N

由于L的范围是确定的,所以每次L的偏移量是O(√N)

但是r的范围没有确定;r的偏移量是O(N)。

那么从一个块到另一个块呢?

明显地,r我们不需要作考虑,仍然是O(N)。

而L明显最多也是2*√N,而且这种情况下,很快就会到下下一块。所以也是O(√N)

由于有根号n个块,所以r的总偏移量是O(N*√N)

而M个询问,每个询问都可以让L偏移O(√N),所以L的总偏移量O(M*√N)

注意了,时间复杂度分析的时候一定要注意,r的偏移量和询问数目是没有直接关系的。

而L则恰恰相反;L的偏移量我们刚才也说明了,它和块的个数没有直接关系。

所以总的时间复杂度是:

O((N+M)*√N)

很神奇地看到了,我们仅仅改变了一下问题求解的次序,就让时间复杂度大幅度下降!

也就是L最大移动M*(sqrt(n)),R每个快R总共最大移动n,那就是(根号n)*n,复杂度加起来就行了

下面是牛客网邀请大牛出的题

Given a sequence of integers a1, a2, ..., an and q pairs of integers (l1, r1), (l2, r2), ..., (lq, rq), find count(l1, r1), count(l2, r2), ..., count(lq, rq) where count(i, j) is the number of different integers among a1, a2, ..., ai, aj, aj + 1, ..., an.

输入描述:

The input consists of several test cases and is terminated by end-of-file.
The first line of each test cases contains two integers n and q.
The second line contains n integers a1, a2, ..., an.
The i-th of the following q lines contains two integers li and ri.

输出描述:

For each test case, print q integers which denote the result.

 

示例1

输入

复制

3 2
1 2 1
1 2
1 3
4 1
1 2 3 4
1 3

输出

复制

2
1
3

备注:

* 1 ≤ n, q ≤ 105
* 1 ≤ ai ≤ n
* 1 ≤ li, ri ≤ n
* The number of test cases does not exceed 10.

下面是我的代码

#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
struct queu
{
    int l,r,id;
}qq[100002];
int a[200002],f[100003],ans=0;
bool cmp(queu a,queu b)
{
    return a.l/1000==b.l/1000 ?a.r<b.r:a.l<b.l;
}
void add(int p)
{
    if(f[p]==0)
    {
        ans++;
    }
    f[p]++;
    return;
}
void dec(int pp)
{
    if(f[pp]==1)
        ans--;
    f[pp]--;
    return;
}
int main()
{
    int n,q,l,r;
    while(~scanf("%d%d",&n,&q))
    {ans=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
        }
        for(int i=n+1;i<=2*n;i++)
        {
            a[i]=a[i-n];
        }
        memset(f,0,sizeof(f));
        int ll=1;
        int rr=0;
        for(int i=0;i<q;i++)
        {
            scanf("%d%d",&l,&r);
            qq[i].l=r;
            qq[i].r=l+n;
            qq[i].id=i;
        }
    int anss[100002]={0};
        sort(qq,qq+q,cmp);
        for(int i=0;i<q;i++)
        {  while(qq[i].r>rr)add(a[++rr]);
            while(qq[i].r<rr)dec(a[rr--]);
            while(qq[i].l<ll)add(a[--ll]);
            while(qq[i].l>ll)dec(a[ll++]);
            anss[qq[i].id]=ans;
        }
        for(int i=0;i<q;i++)
        {
            printf("%d\n",anss[i]);
        }
    }
}

模板:

#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
struct queu
{
    int l,r,id;
}qq[100002];
int a[200002],f[100003],ans=0;
bool cmp(queu a,queu b)
{
    return a.l/1000==b.l/1000 ?a.r<b.r:a.l<b.l;
}
void add(int p)
{
    if(f[p]==0)
    {
        ans++;
    }
    f[p]++;
    return;
}
void dec(int pp)
{
    if(f[pp]==1)
        ans--;
    f[pp]--;
    return;
}
int main()
{
    int n,q,l,r;
    while(~scanf("%d%d",&n,&q))
    {ans=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
        }
        memset(f,0,sizeof(f));
        int ll=1;
        int rr=0;
        for(int i=0;i<q;i++)
        {
            scanf("%d%d",&l,&r);
            qq[i].l=l;
            qq[i].r=r;
            qq[i].id=i;
        }
    int anss[100002]={0};
        sort(qq,qq+q,cmp);
        for(int i=0;i<q;i++)
        {  while(qq[i].r>rr)add(++rr);
            while(qq[i].r<rr)dec(rr--);
            while(qq[i].l<ll)add(--ll);
            while(qq[i].l>ll)dec(ll++);
            anss[qq[i].id]=ans;
        }
        for(int i=0;i<q;i++)
        {
            printf("%d\n",anss[i]);
        }
    }
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值