莫队算法(普通莫队)智慧暴力例题模板

1 基础莫队算法
  莫队算法 = 离线 + 暴力 + 分块。
  “离线”和“在线”的概念。在线是交互式的,一问一答;如果前面的答案用于后面的提问,称为“强制在线”。离线是非交互的,一次性读取所有问题,然后一起回答,"记录所有步,回头再做”。
   基础的莫队算法是一种离线算法,它通常用于不修改只查询的一类区间问题,复杂度O (n√n),没有在线算法线段树或树状数组好,但是编码很简单。

2.莫队算法实现
  莫队算法把排序做了简单的修改,就把暴力法的复杂度从O(mn)提高到O (n√n)
  (1)暴力法的排序:把查询的区间按左端点排序,如果左端点相同,再按右端点排序。
   莫队算法的排序:把数组分块(分成√n块),然后把查询的区间按左端点所在块的序号排序,如果左端点的块相同,再按右端点排序(注意不是按右端点所在的块排序)。
   除了排序不一样,莫队算法和暴力法的其他步骤完全一样。
   (2)如果我们知道区间[L,R],就能在O(1)求出[L−1,R],[L+1,R],[L,R−1],[L,R+1]的话,那就可以用莫队算法了。
   所以我们要解决的问题只有两个
   ①如何将读入的查询排序。

bool cmp(node a,node b)
{
    if(pos[a.l]!=pos[b.l])return pos[a.l]<pos[b.l];//按L所在的块排序,如果块相等再按R排序
    if(pos[a.l]&1)return a.r<b.r;//奇偶性优化,优化性能,奇数块r从小到大,偶数块r从大到小
    return a.r>b.r;
}

注意:因为最后要按输入次序输出,所以要定义一个k储存输入的次序。
   ②如何将当前询问的答案从上一个询问的答案转移过来。

void add(int x)//扩大区间时(L左移或R右移),增加数x出现的次数
{
    cnt[a[x]]++;
    if(cnt[a[x]]==1)ANS++;//这个元素第一次出现
}
void del(int x) //缩小区间时(L右移或R左移),减少数x出现的次数
{
    cnt[a[x]]--;
    if(!cnt[a[x]])ANS--;//这个元素消失了
}
for(int i=1;i<=m;i++)//莫队算法核心
    {
        while(L>q[i].l)add(--L);//扩大区间,L左移
        while(R<q[i].r)add(++R);//扩大区间,R右移
        while(L<q[i].l)del(L++);//缩小区间,L右移
        while(R>q[i].r)del(R--);//缩小区间,R左移
        //思考为什么扩大区间是符号在前,缩小区间是符号在后
        //因为扩大区间是增加下一个数,而减小区间是删除现在这个数
        ans[q[i].k]=ANS;//按输入次序储存答案
    }

3.小优化:奇偶性排序
在这里插入图片描述
奇数块从小到大,偶数块从大到小和奇数块从大到小,偶数块从小到大都是可行的,下面的例题我使用的是前者。
这样能快是因为右指针移到右边后不用再跳回左边,而跳回左边后处理下一个块又要跳回右边,这样能减少一半操作,理论上能快一倍。

下面是一道例题
题目描述
HH 有一串由各种漂亮的贝壳组成的项链。HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含义。HH 不断地收集新的贝壳,因此,他的项链变得越来越长。有一天,他突然提出了一个问题:某一段贝壳中,包含了多少种不同的贝壳?这个问题很难回答……因为项链实在是太长了。于是,他只好求助睿智的你,来解决这个问题。

输入输出格式
输入格式:
第一行:一个整数N,表示项链的长度。

第二行:N 个整数,表示依次表示项链中贝壳的编号(编号为0 到1000000 之间的整数)。

第三行:一个整数M,表示HH 询问的个数。

接下来M 行:每行两个整数,L 和R(1 ≤ L ≤ R ≤ N),表示询问的区间。

输出格式:
M 行,每行一个整数,依次表示询问对应的答案。

输入输出样例
输入样例#1:
6
1 2 3 4 3 5
3
1 2
3 5
2 6

输出样例#1:
2
2
4

说明
数据范围:
对于100%的数据,N <= 500000,M <= 500000。

#include <iostream>
#include <cmath>
#include <algorithm>
#define maxn 1000000+10
using namespace std;
struct node
{
    int l,r,k;//区间[l,r]以及出现的次序k
}q[maxn];
int cnt[maxn];//统计数字i出现的次数
int pos[maxn];//储存第i个元素所在的块
int ans[maxn];//记录答案
int a[maxn];//储存序列
int ANS=0;
bool cmp(node a,node b)
{
    if(pos[a.l]!=pos[b.l])return pos[a.l]<pos[b.l];//按L所在的块排序,如果块相等再按R排序
    if(pos[a.l]&1)return a.r<b.r;//奇偶性优化,优化性能,奇数块r从小到大,偶数块r从大到小
    return a.r>b.r;
}
void add(int x)//扩大区间时(L左移或R右移),增加数x出现的次数
{
    cnt[a[x]]++;
    if(cnt[a[x]]==1)ANS++;//这个元素第一次出现
}
void del(int x) //缩小区间时(L右移或R左移),减少数x出现的次数
{
    cnt[a[x]]--;
    if(!cnt[a[x]])ANS--;//这个元素消失了
}
int main()
{
    int n,m;
    cin>>n;
    int block=sqrt(n);//每块的大小
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        pos[i]=(i-1)/block+1; //第i个元素所在的块
    }
    cin>>m;
    for(int i=1;i<=m;i++)
    {
        cin>>q[i].l>>q[i].r;
        q[i].k=i;//记录查询的原始顺序
    }
    sort(q+1,q+m+1,cmp);
    int L=1,R=0;//左右指针的初始值
    for(int i=1;i<=m;i++)//莫队算法核心
    {
        while(L>q[i].l)add(--L);//扩大区间,L左移
        while(R<q[i].r)add(++R);//扩大区间,R右移
        while(L<q[i].l)del(L++);//缩小区间,L右移
        while(R>q[i].r)del(R--);//缩小区间,R左移
        //思考为什么扩大区间是符号在前,缩小区间是符号在后
        //因为扩大区间是增加下一个数,而减小区间是删除现在这个数
        ans[q[i].k]=ANS;//按输入次序储存答案
    }
    for(int i=1;i<=m;i++)
    {
        cout<<ans[i]<<endl;//按输入次序输出答案
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值