莫队算法入门

Talk about 莫队

莫队算法,是莫涛dalao发明的一个神奇的优化暴力算法,它使用看似很simple的指针移动操作以及分块的思想来将复杂度优化至\(O(n\sqrt n)\)

莫队的基本思想也很简单:

  1. 离线操作,在后面会提到我们通过排序来降低复杂度
  2. 设之前我们以及求出了区间\([l,r]\)的答案,那么我们考虑如何快速转移到\([l+1,r],[l-1,r],[l,r-1],[l,r+1]\)
  3. 每一次利用之前的信息跳动指针即可得出答案

不过如果是这样的话,只要出题人把数据造坑一点,让你\(l,r\)指针一直左右移动,就可以卡到\(O(n^2)\)我还不如写暴力

所以莫队的精髓来了,既然都是询问,那我们是否可以通过适当地改变询问的顺序来让\(l,r\)跳转的幅度更小一点。

所有我们可以利用分块的思想来优化:对于两个询问,若在其\(l\)在同块,那么将其\(r\)作为排序关键字,若\(l\)不在同块,就将\(l\)作为关键字排序(这就是双关键字)

这样就可以优化时间复杂度么,我们看一下严格的证明(摘自大米饼的博客):

首先,枚举\(m\)个答案,就一个\(m\)了。设分块大小为\(unit\),元素\(i\)所属的快为\(blk_i\)

分类讨论:

①\(l\)的移动:若下一个询问与当前询问的\(l\)所在的块不同,那么只需要经过最多\(2\cdot unit\)步可以使得\(l\)成功到达目标.复杂度为:\(O(m\cdot unit)\)

②\(r\)的移动:\(r\)只有在\(blk_l\)相同时才会有序(其余时候还是疯狂地乱跳,你知道,一提到乱跳,那么每一次最坏就要跳\(n\)次!),\(blk_l\)什么时候相同?在同一块里面\(blk_i\)相同。对于每一个块,排序执行了第二关键字: \(r\)。所以这里面的\(r\)是单调递增的,所以枚举完一个块,\(r\)最多移动n次。总共有\(\frac{n}{unit}\)个块:复杂度为:\(O(\frac{n^2}{unit})\)

总结:\(O(n\cdot unit+\frac{n^2}{unit})\)(\(n,m\)同级,就统一使用\(n\))

根据基本不等式得:当\(n=\sqrt n\)时,得到莫队算法的真正复杂度:\(O(n\sqrt n)\)

然后除此以外莫队还有一个更加NB的常数优化,即在cmp时写成:

return blk[a.l]<blk[b.l]||(blk[a.l]==blk[b.l]&&(blk[a.l]&1?a.r<b.r:a.r>b.r));

其实也很好理解吧,当左端点同块时判断一下当前快编号的奇偶性,尽量让右端点波动范围较小(类似波浪形)


经典板子题——区间不同元素个数

板子题参考:SPOJ D-query

大致题意:给定一个数组,每次询问一个区间内有多少不同的元素

很简单的莫队板子题,对于每一次加入新的数时都判断一下这个数之前是否出现过即可,删除时同理。

别忘记离散化,CODE

#include<cstdio>
#include<cctype>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=30005,M=200005;
struct data
{
    int l,r,ans,id;
}q[M];
int a[N],b[N],n,m,size,tot,blk[N],res,L,R,cnt[N];
inline char tc(void)
{
    static char fl[100000],*A=fl,*B=fl;
    return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(int &x)
{
    x=0; char ch; while (!isdigit(ch=tc()));
    while (x=(x<<3)+(x<<1)+ch-‘0‘,isdigit(ch=tc()));
}
inline void write(int x)
{
    if (x>9) write(x/10);
    putchar(x%10+‘0‘);
}
inline bool cmp1(data a,data b)
{
    return blk[a.l]<blk[b.l]||(blk[a.l]==blk[b.l]&&(blk[a.l]&1?a.r<b.r:a.r>b.r));
}
inline bool cmp2(data a,data b)
{
    return a.id<b.id;
}
inline int find(int x)
{
    int l=1,r=tot,mid;
    while (l<=r)
    {
        mid=l+r>>1; if (b[mid]==x) return mid;
        if (b[mid]<x) l=mid+1; else r=mid-1;
    }
}
inline void add(int col)
{
    if (++cnt[col]==1) ++res;
}
inline void del(int col)
{
    if (--cnt[col]==0) --res;
}
int main()
{
    //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
    register int i; read(n); size=sqrt(n);
    for (i=1;i<=n;++i) read(a[i]),b[i]=a[i],blk[i]=(i-1)/size+1;
    sort(b+1,b+n+1); tot=unique(b+1,b+n+1)-b-1;
    for (i=1;i<=n;++i) a[i]=find(a[i]);
    for (read(m),i=1;i<=m;++i) read(q[i].l),read(q[i].r),q[i].id=i;
    sort(q+1,q+m+1,cmp1); L=q[1].l; R=q[1].r;
    for (i=L;i<=R;++i) add(a[i]); q[1].ans=res;
    for (i=2;i<=m;++i)
    {
        while (L>q[i].l) add(a[--L]); while (L<q[i].l) del(a[L++]);
        while (R<q[i].r) add(a[++R]); while (R>q[i].r) del(a[R--]);
        q[i].ans=res;
    }
    for (sort(q+1,q+m+1,cmp2),i=1;i<=m;++i) write(q[i].ans),putchar(‘\n‘);
    return 0;
}

原文地址:https://www.cnblogs.com/cjjsb/p/9539388.html

时间: 2024-10-09 01:18:13

莫队算法入门的相关文章

HYSBZ 2038 小Z的袜子(hose) (莫队算法入门)

题意:取一段区间,求区间中任取两个数相同的概率: 思路:所求概率P=(A*(A-1)/2+B*(B-1)/2+......)/(R-L+1)*(R-L)/2化简得P=(A*A+B*B+......+Z*Z-(R-L+1))/(R-L+1)*(R-L); 将询问区间左端点放在同一分块中处理,每次处理一个块中的所有询问,对于同一块,询问右端点按严格递增处理,左端点不断移动: #include<cstdio> #include<cstring> #include<cmath>

【莫队算法】

·通过排序巧妙优化复杂度,NOIP前的最后一丝宁静. ·目前的题型概括为三种:普通莫队,树形莫队以及带修莫队. 若谈及入门,那么BZOJ2038的美妙袜子一题堪称经典. [例题一]袜子 ·题目大意:      进行区间询问[l,r],输出该区间内随机抽两次抽到相同颜色袜子的概率. ·分析:      首先考虑对于一个长度为n区间内的答案如何求解.题目要求Ans使用最简分数表示:那么分母就是n*n(表示两两袜子之间的随机组合),分子是一个累加和,累加的内容是该区间内每种颜色i出现次数sum[i]的

莫队算法学习小记

算法创始人 莫涛大神. 莫涛队长的算法,%%%%%%%%% 算法简介 算法前提 可以在O(1)的时间内把[l,r]的询问转移到[l-1,r],[l+1,r],[l,r-1],[l,r+1]的询问,而且不需要修改操作,那么就可以使用莫队算法([a,b]表示从a到b的区间,包含a和b) 算法核心 假如有一个询问[l,r]要转移到一个询问[l1,r1],那么需要的时间为O(|l1?l|+|r1?r|),在算法前提下,可以用这么多的时间暴力转移. 但是可以发现有时候有些点会被来回算很多次,这样大量浪费了

莫队算法

Beautiful Girl 题意 给定一个长度为 n 的序列 a[1], a[2], ..., a[n] . m 组询问 (l, r, K) , 求区间 [l, r] 去除重复的数之后的第 K 小. n, m <= 100000 . 分析 莫队算法 + 值域分块. 1 #include <cstdio> 2 #include <cstring> 3 #include <cstdlib> 4 #include <cctype> 5 #include &

BZOJ4241 历史研究 莫队算法 堆

欢迎访问~原文出处--博客园-zhouzhendong&AK 去博客园看该题解 题目 Description IOI国历史研究的第一人--JOI教授,最近获得了一份被认为是古代IOI国的住民写下的日记.JOI教授为了通过这份日记来研究古代IOI国的生活,开始着手调查日记中记载的事件. 日记中记录了连续N天发生的时间,大约每天发生一件. 事件有种类之分.第i天(1<=i<=N)发生的事件的种类用一个整数Xi表示,Xi越大,事件的规模就越大. JOI教授决定用如下的方法分析这些日记: 1.

CodeForces - 86D 莫队算法

http://codeforces.com/problemset/problem/86/D 莫队算法就是调整查询的顺序,然后暴力求解. 每回可以通过现有区间解ans(l,r)得到区间(l+1,r),(l-1,r),(l,r+1),(l,r-1)的区间解. 调整方式http://blog.csdn.net/bossup/article/details/39236275 这题比那个还要简单,查询的是K^2*Z,很清楚就是莫队算法,然而做的时候没有学过,回来补题补到 关键是我一直没明白为什么重载小于号

codeforces 617 E. XOR and Favorite Number(莫队算法)

题目链接:http://codeforces.com/problemset/problem/617/E 题目: 给你a1 a2 a3 ··· an 个数,m次询问:在[L, R] 里面又多少中 [l, r] 使得 al xor al+1 xor ··· ar 为 k. 题解: 本题只有区间查询没有区间修改,而且数据量不大(10w),所以可以用离线的方法解决. 使用莫队算法来解决,就需要O(1)的修改[L, R+1] .[L, R-1].[L+1, R].[L-1, R]. 详细的莫队可以百度学一

(普通的)莫队算法简单介绍

莫队算法(由莫涛发明的)是一种离线的暴力算法(至少我这么认为).使用莫队算法的条件是,知道一个区间[l, r]的结果,那么也可以快速知道[l + 1, r],[l - 1, r], [l, r - 1], [l, r + 1]这四个区间的结果.于是可以想到,直接通过这样转移来解决一些问题.当然有些出题人机智,故意卡这种暴力,让你从头跑到尾然后从尾跑到头,于是时间复杂度高达O(n2) 而莫队算法就是通过改变处理询问的顺序来降低时间复杂度. 比如说现在知道一个区间[l1, r1],又要转移到[l2,

莫队算法良心讲解

问题:有n个数组成一个序列,有m个形如询问L, R的询问,每次询问需要回答区间内至少出现2次的数有哪些. 朴素的解法需要读取O(nm)次数.如果使用STL的Map来保存出现的次数,每次需要O(nmlogn)的复杂度.有没有更快的方法呢? 注意到询问并没有强制在线,因此我们可以使用离线方法.注意到一点,如果我们有计算完[L, R]的map,那么[L - 1, R].[L + 1, R].[L, R - 1].[L, R + 1]都能够在O(logn)的复杂度得出.是否能安排适当的询问顺序,使得每次