莫队算法

本文深入解析莫队算法,一种通过分块实现的离线区间操作算法,时间复杂度为O(n*sqrt(n))。文章详细介绍了算法的排序策略、查询流程及其实现细节,并通过具体题目演示算法的应用过程,帮助读者掌握算法核心思想。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

莫队算法是一个用分块实现的暴力算法,只支持离线区间操作。时间复杂度 O ( n*sqrt(n) )

算法思路参考:https://www.cnblogs.com/WAMonster/p/10118934.html

主要就是先对查询的区间排序,先按左端点所在块从小到大排序,再按右端点从小到大排序。

查询时用 L 和 R 来扫描头尾,初始L=1,R=0;

一个数组,两个查询区间Q1和Q2,求这两个区间分别存在多少种不同的数字.

下图是参考博客里的,可以帮助理解。

 

跳过几步之后。。。

第一个区间查询完毕,开始查询第二个区间,同样开始移动L和R

移动L,删去1,但是我们发现1不只出现了一次,所以总数不变。 

当L移动到4时,删除的数出现次数都不为1,总数不变. 

下图总数应为4,写错了 

第二个区间查询完毕。

#include <cstdio>
#include <algorithm>
#include <vector>
#include <iostream>
#include <map>
#include <queue>
#include <cstring>
#include <cmath>
using namespace std;
const int inf =0x3f3f3f3f;
const int maxn = 1e5 + 5;
typedef long long ll;
struct node
{
    int l, r, id;
} p[maxn << 1];
int pos[maxn];//分块
int vis[maxn]; //可以记录数据次数或者其他
int ans[maxn];  //记录答案
bool cmp(node a, node b)
{
    if (pos[a.l] == pos[b.l])
        return a.r < b.r;
    return pos[a.l] < pos[b.l];
}
int res, a[maxn];  //res记录总数
void add(int i)  //根据题意修改这两个模块
{
   //增加
}
void del(int i)
{
   //删除
}
int main()
{
    int n, q;
    cin >> n >> q;
    int dis = sqrt(n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]), pos[i] = i / dis;  //分块
    for (int i = 0; i < q; i++)
    {
        scanf("%d %d", &p[i].l, &p[i].r);
        p[i].id = i;
    }
    sort(p, p + q, cmp);
    int l = 1, r = 0;
    for (int i = 0; i < q; i++)
    {
        while (r < p[i].r)
            add(++r);
        while (r > p[i].r)
            del(r--);
        while (l < p[i].l)
            del(l++);
        while (l > p[i].l)
            add(--l);
        ans[p[i].id] = res;
    }
    for (int i = 0; i < q; i++)
        printf("%d\n", ans[i]);
}

思路就是这样,我们来看个题练下吧。 

题目链接

 

题意:求某个区间内数字的值和出现次数相同的数字个数

输入:

第一行包含两个空格分隔的整数n和m(1≤n, m≤10^5)——数组a的大小和对它的查询数。下一行包含n个用空格分隔的正整数a1 a2…, an(1≤ai≤10^9)。接下来的m行包含查询的描述,每行一个。这些行的第j个包含了第j个查询的描述,它是两个以空格分隔的整数lj和rj(1≤lj≤rj≤n)。

输出 

在m行中打印m个整数——查询的答案。第j行应该包含第j个查询的答案。

例:

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

思路:vis数组记录出现次数,如果vis[ i ]=i,总数+1,如果vis[ i ] = i +1,代表之前它是符合要求的,总数+1过,但是现在又不符合要求了,所以总数-1;

本题数据需要离散化。

代码:

#include <cstdio>
#include <algorithm>
#include <vector>
#include <iostream>
#include <map>
#include <queue>
#include <cstring>
#include <cmath>
using namespace std;
const int inf =0x3f3f3f3f;
const int maxn = 1e5 + 5;
typedef long long ll;
struct node
{
    int l, r, id;
} p[maxn << 1];
vector<int>v;
int pos[maxn];//分块
int vis[maxn]; //可以记录数据次数或者其他
int ans[maxn];  //记录答案
int res=0, a[maxn],b[maxn];  //res记录总数
int getid(int x)  //离散化
{
    return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
bool cmp(node a, node b)
{
    if (pos[a.l] == pos[b.l])
        return a.r < b.r;
    return pos[a.l] < pos[b.l];
}

void add(int i)  //根据题意修改这两个模块
{
    vis[b[i]]++;
    if(vis[b[i]]==a[i])
        res++;
    else if(vis[b[i]]-1==a[i])  //如果加上这个点之后不符合要求并且没加上这个点时符合要求的话,总数减1
        res--;
}
void del(int i)
{
    vis[b[i]]--;
    if(vis[b[i]]==a[i])
        res++;
    else if(vis[b[i]]+1==a[i])  //如果减去这个点之后不符合要求并且没减去这个点时符合要求的话,总数减1
        res--;

}
int main()
{
    int n, q;
    cin >> n >> q;
    int dis = sqrt(n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]), pos[i] = i / dis,v.push_back(a[i]); //分块
    sort(v.begin(),v.end()),v.erase(unique(v.begin(),v.end()),v.end());  //离散化
    for (int i = 0; i < q; i++)
    {
        scanf("%d %d", &p[i].l, &p[i].r);
        p[i].id = i;
    }
    for(int i=1; i<=n; i++)
        b[i]=getid(a[i]);
    sort(p, p + q, cmp);
    int l = 1, r = 0;
    for (int i = 0; i < q; i++)
    {
        while (r < p[i].r)
            add(++r);
        while (r > p[i].r)
            del(r--);
        while (l < p[i].l)
            del(l++);
        while (l > p[i].l)
            add(--l);
        ans[p[i].id] = res;
    }
    for (int i = 0; i < q; i++)
        printf("%d\n", ans[i]);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值