首页 > 其他分享 >莫队

莫队

时间:2023-03-16 20:25:00浏览次数:24  
标签:int 复杂度 端点 区间 排序 莫队

对于区间修改,区间查询,我们知道有线段树(链状),RMQ,树状数组,分块,树剖(树形结构)...尽管它们很优秀,但是在处理一些区间问题上,仍然有所不足。

事实上,如果题目不要求在线,存在一种及其优秀的算法:莫队。

什么是莫队?

它由前国家队队长莫涛率先归纳总结,并将其命名为“莫队”。

传说中它是一种“能解决一切区间问题”的算法。

可离线、无修改的莫队

莫队算法其实我们并不陌生,大家在处理区间问题时或多或少使用过莫队的思想。

莫队算法的精髓就是通过合理地对询问排序,然后以较优的顺序暴力回答每个询问。处理完一次询问后,可以使用它的信息得到下一个询问区间的答案。

考虑这个问题:已知区间 [1,5] 的答案,求 [2,6] 的答案,如何暴力去求?

当然,最暴力的做法可以将区间 [2,6] 从头到尾扫一遍;也可以在区间 [1,5] 的基础上,去掉位置1(即将左端点右移一位),加上位置6(即将右端点右移一位),得到区间 [2,6] 的答案。

第二种方法好像比前一种方法有所优化,但是具体优化了多少?我们还不知道。但是我们知道,经由“合理的排序”,可以尽可能保证每个点被重复的次数最少,这就是莫队的基本思想。

接下来我们还需要思考一个问题:什么是“合理的排序”?

假设题目给了许多个查询区间,为了保证每个点被重复的次数最少,我们先按照区间端点从小到大排序;如果左端点相同,那么按照端点从小到大排序。这样貌似是最好的排序方案了,其实不然,左端点不会“回车”,右端点未必,假设右端点时不时“回车”,这样比起一般暴力,并没有优化多少。

莫队提供了这样一个排序方案:将原序列以√n 为一块进行分块,排序按照上方方案排序。然后我们根据上面提到的“移动当前区间左右端点”的方法,按顺序求每个询问区间的答案,移动每一个询问区间左右端点可以求出下一个区间的答案。

核心代码:

sort(q+1,q+1+m);
int ql=1,qr=0;//初始区间是一个空区间
for(int i=1;i<=m;++i){
    while(pl<q[i].l) del(a[pl++];
    while(pl>q[i].l) add(a[--pl]);
    while(pr<q[i].r) add(a[++pr];
    while(pr>q[i].r) del(a[pr--]);
    ans[q[i].id]=sum;
}

这样就可以求出答案了!

那么,这样做的复杂度是什么?

显然,每次移动左端点(或右端点)的复杂度都是Ο(1)。那么只需要知道左右端点分别移动了多少次,就可以知道复杂度了。

对于右端点:当当前询问的左端点在同一块时,右端点是有序的,那么右端点最多会从1一直移动到n:两个询问左端点在不同块时(即“跨块”)时,最多从n回到1。两种都是Ο(n)的,总共有根号n块,所以复杂度是O(n根号n)。

对于左端点:当当前询问的左端点在同一块时,注意左端点不是有序的,那么一次最多从块的一段移到另一端,复杂度O(根号n),总共有n个询问的话,复杂度是O(根号n)。跨块时也类似,移动距离也是O(根号n)。

综上,莫队的复杂度是O(根号n)。

莫队的一大优点是:代码思路及其简单,尤其是序列上无需修改的莫队,核心代码只有短短5行。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,m,sum;
int a[N],ans[N],cnt[N];
struct query{
    int id,l,r;
}q[N];
bool operator(query i,query j){
    if(i.l!=j.l) return i.l<j.r;
    return i.r<j.r;
}
void add(int x){
    if(!cnt[x]) sum++;
    cnt[x]++;
}
void del(int x){
    cnt[x]--;
    if(!cnt[x]) sum--;
}
int main(){
    cin>>n;
    for(int i=1;i<=n;++i) cin>>a[i];
    cin>>m;
    for(int i=1;i<=m;++i)
        q[i].id=i,cin>>q[i].l>>q[i].r;
    sort(q+1,q+1+m);
    int pl=1,pr=0;
    for(int i=1;i<=m;++i){
        while(pl<q[i].l) del(a[pl++]);
        while(pl>q[i].l) add(a[--pl]);
        while(pr<q[i].r) add(a[++pr]);
        while(pr>q[i].r) del(a[pr--]);
        ans[q[i].id]=sum;
}
    for(int i=1;i<=m;++i) cout<<ans[i]<<endl;
    return 0;
}

可以单点修改的莫队

写完上面这道题,可以发现:普通的莫队算法没有支持修改。那么如何改造该算法使它支持修改呢?

莫队算法被称为“优雅的暴力”,那么我们改造莫队算法的思路也只有一个:改造询问排序的方式,然后继续暴力。

这一次,排序的方式:以

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

标签:int,复杂度,端点,区间,排序,莫队
From: https://www.cnblogs.com/buleeyes/p/17223994.html

相关文章

  • CF375D Tree and Queries - 树上莫队 -
    题目链接:https://codeforces.com/contest/375/problem/D题解:询问的子树可以看成求出dfs序之后的一段连续序列,因此可以使用树上莫队。首先将dfs序求出来,对于每个点,计......
  • 【YBT2023寒假Day15 C】缺口一样(数论)(莫队)(根号分治)
    缺口一样题目链接:YBT2023寒假Day15C题目大意给你一个序列,多次询问,每次问你一个区间这里面所有非空点集的最大公约数之积,对质数取模。思路首先质因子之间是独立的,考虑......
  • 莫队
    极好的bloghttps://www.cnblogs.com/WAMonster/p/10118934.html概述一下,就是区间上的双指针+分块+排序优化的优美暴力复杂度O(nsqrt(n))tobecontinue.........
  • 分块莫队学习笔记
    适用情况:对于序列A中某个子区间的问题,当遇到像众数类似的不满足区间合并的性质的数据,或者一些用线段树、树状数组比较难维护的数据时,使用分块莫队。实际上是对暴力的一种......
  • 莫队
    简介莫队是一种优美的暴力算法~(——byDaniel_yuandalao)。例题例题出自:莫队莫队大全[数据结构]莫队(建议按顺序刷题~)P3901数列找不同分析板子,速速切掉!!!代码点......
  • 简单分块与莫队
    1-分块1.1-定义分块是将要维护的信息分成若干块,而后通过维护整块的信息或者是块间的信息来优化算法。1.2-序列分块在序列上以线段树来类比,线段树是将序列每次对......
  • 莫队算法学习(转载)
    1.https://blog.csdn.net/Just__Do__IT__/article/details/118991059?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522167326430316782428668181%2522%252C%252......
  • 莫队trick
    不带修(belong[a.l]^belong[b.l])?(belong[a.l]<belong[b.l]):((belong[a.l]&1)?a.r<b.r:a.r>b.r)待修(belong[a.l]^belong[b.l])?belong[a.l]<......
  • 莫队算法
    概念莫队算法是由莫涛提出的算法,可以解决一类离线区间询问问题,适用性极为广泛。同时将其加以扩展,便能轻松处理树上路径询问以及支持修改操作。假设\(n=m\),对于序列上的......
  • 带修改的莫队算法学习小记
    简介莫涛大神创造出的离线询问算法的带修改版。算法基础:需要掌握​​莫队算法​​,会打暴搜(暴力)。一个叫莫的双端队列。只支持单点修改操作方法普通的不带修改的莫队算......