莫队算法,修改莫队 学习笔记

莫队算法简介

莫队算法由队长莫涛发明,是区间修改的神器(其实是暴力

莫队算法可以解决的经典问题

给出一个长度为n的区间,每次查询 [l,r] [ l , r ] 有多少种颜色之类的问题;
我们随机一个数据 1,2,3,4,5,5,6,6,6 1 , 2 , 3 , 4 , 5 , 5 , 6 , 6 , 6
询问 [1,3],[2,2],[4,5],[4,6],[5,9],[6,8] [ 1 , 3 ] , [ 2 , 2 ] , [ 4 , 5 ] , [ 4 , 6 ] , [ 5 , 9 ] , [ 6 , 8 ] 就这样了

考虑暴力算法

每一次直接从枚举区间 [l,r] [ l , r ] 就可以

莫队算法

我们假设已经知道了区间 [l,r] [ l , r ] 的答案,那么我们就可以 O(1) O ( 1 ) 的时间复杂度内求出 [l,r+1][l,r1][l+1,r][l1,r] [ l , r + 1 ] [ l , r − 1 ] [ l + 1 , r ] [ l − 1 , r ] 的答案,对于上面那一个问题,我们先把所有的询问都读入进来(这也就说明了莫队算法是一个离线算法,如果有强制在线的莫队算法就无能为力了)。按照一定的规则排序
怎么排序呢? 分块
我们把序列分块,然后按照询问的左端点所在的块作为第一关键字,询问的右端点作为第二关键字进行排序;
先分块一般分 n n 块,莫队的时间复杂度是玄学,取决于块的大小,有时大点好,有时小点好
分块之后是 [1,2,3][4,5,6][7,8,9] [ 1 , 2 , 3 ] [ 4 , 5 , 6 ] [ 7 , 8 , 9 ] 这个是按照下标排序的
把上面询问排序之后是 [2,2],[1,3],[4,5],[4,6],[6.8],[5.9] [ 2 , 2 ] , [ 1 , 3 ] , [ 4 , 5 ] , [ 4 , 6 ] , [ 6.8 ] , [ 5.9 ]
然后用两个指针 nowl,nowr n o w l , n o w r 来记录当前所处理的区间的左右端点(初始化一般为0),按排序从左到右进行处理询问。

  • 若当前左指针小于所处理区间的左端点,不断减去当前指针所指的位置对答案的贡献,然后不断把指针移向左端点
  • 若当前左指针大于所处理区间的左端点,不断加上当前指针所指的位置对答案的贡献,然后不断把指针移向左端点
  • 若当前右指针小于所处理区间的右端点,不断加上当前指针所指的位置对答案的贡献,然后不断把指针移向右端点
  • 若当前右指针大于所处理区间的右端点,不断减去当前指针所指的位置对答案的贡献,然后不断把指针移向右端点

实现可以看后面例题的代码
——————莫队核心;
图例应该能看懂吧

莫队算法时间复杂度分析

莫队算法采取了分块的方法进行排序,排序非瓶颈

  1. 考虑询问的左端点位于同一块的时候,右指针在同一个块中最多移动 n n 次 , 一共分为了 n n 个块,复杂度为 O(nn) O ( n n )
  2. 考虑询问右端点,由于在所有块中右端点为递增的顺序,所以在每一个块中右端点最多转移 n n 次,一共分了n个块,复杂度为 O(nn) O ( n n ) ;
  3. 考虑如果询问区间跨越块的话,每一个块中左端点最多可以增加 n n 次,一共 n n 个块,复杂度O(nn).
    综上莫队的时间复杂度为 O(nn) O ( n n ) .

例题

[SDOI2009]HH的项链 from洛谷
这个题是莫队的模板题,直接按照上面的做就可以,代码附详解

#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cmath>
const int MAXN=1e6;
using namespace std;
int cur;
struct Node
{
    int l,r,pos;
    friend bool operator < (Node A,Node B)
    {
        if(A.l/cur==B.l/cur) return A.r<B.r;//若询问的区间左端点在一个块内,按右端点排序 
        else return A.l/cur<B.l/cur;//反之按左端点排序 
    }
}q[MAXN];
int n,m,val[MAXN],ans[MAXN],num[MAXN],answer;
inline void Remove(int x) { if(--num[val[x]]==0) answer--; }//去除,若==0则说明对答案有贡献,答案-1 
inline void Add(int x){ if(++num[val[x]]==1) answer++; }//增加,若==1说明没出现过,对答案有贡献,答案+1 
inline int read()
{
    int x=0;
    char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x;
} 
int main()
{
        n=read();
        for(int i=1;i<=n;i++) val[i]=read();
        m=read();
        for(int i=1;i<=m;i++)
        {
            q[i].l=read(),q[i].r=read(); 
            q[i].pos=i;
        }
        cur=sqrt(n);//块的数量 
        sort(q+1,q+m+1);//排序 
        int nowl=0,nowr=0; 
        for(int i=1;i<=m;i++)
        {
            while(nowl<q[i].l) Remove(nowl++);
            while(nowr>q[i].r) Remove(nowr--);
            while(nowl>q[i].l) Add(--nowl);
            while(nowr<q[i].r) Add(++nowr);//莫队板子 
            ans[q[i].pos]=answer;
        }
        for(int i=1;i<=m;i++)
        {
            printf("%d\n",ans[i]);
        }   
    return 0;
}

带修改莫队的算法

莫队算法还可以支持修改,我们也是把询问和修改都读入进来,进行离线操作.
对于每一组询问,我们额外维护离这一组询问的最近的修改。
当我们查询到一个区间时,如果当前修改到的大于我们对这一组询问维护的修改,我们就该回去,反之就改过来.
可能有点难理解,看看代码就明白了。

        while(nowl<q[i].l) Remove(nowl++);
        while(nowl>q[i].l) Add(--nowl);
        while(nowr<q[i].r) Add(++nowr);
        while(nowr>q[i].r) Remove(nowr--);
        while(now<q[i].pre) Change_(++now,i);//当前询问的区间的最近修改还没有改,就改
        while(now>q[i].pre) Change_(now--,i);//当前修改的数量已经大于最近询问的修改,改回去
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值