【算法】莫队算法初探

【算法介绍】

  莫队算法是用于离线处理处理区间问题的一类算法,非常易于理解和上手,应用面十分广泛,甚至还可以在树上进行操作。

  当我们得到$[L,R]$的答案之后,如果能够以较低的复杂度扩展得到$[L-1,R],[L+1,R],[L,R-1],[L,R+1]$的答案,我们就可以使用莫队算法,通常这个扩展的复杂度是$O(1)$或$O(logn)$。

  如果我们对于每个询问都暴力移动左右端点,那么复杂度肯定是$O(n^2)$的,而莫队算法的精髓就在于结合了分块的思想。

  设扩展一次的复杂度为$O(f(n))$。将序列分割成$\sqrt{n}$个块,把询问排序,以左端点块编号为第一关键字,右端点为第二关键字,这样扩展的话,对于每一次询问,左端点扩展的复杂度是$O(\sqrt{n}*f(n))$的,而对于左端点的每一个块,右端点扩展的复杂度是$O(n*f(n))$的,可以发现右端点扩展的复杂度是跟询问次数无关的,所以总复杂度为$O((n+m)*\sqrt{n}*f(n))$。

  有一个常数优化是左端点在奇数块的话右端点升序排序,在偶数块的话右端点降序排序,因为这样相当于一个右端点来回跑的过程,能够省去很多次右端点的扩展,能够有明显的效率提升。

【算法流程】

  将询问排序。

  扫一遍询问,从上一次询问的左右端点扩展到当前询问,得出答案。

bool operator < (poi a, poi b)
{return bl[a.l]<bl[b.l] || (bl[a.l]==bl[b.l] && ((bl[a.l]&1)?a.r<b.r:a.r>b.r));}
inline void update(int x, int delta){...//更新信息}
int main()
{
    read(n); read(m); int blo=sqrt(n);
    for(int i=1;i<=n;i++) read(a[i]);
    for(int i=1;i<=m;i++) read(q[i].l), read(q[i].r), q[i].pos=i;
    for(int i=1;i<=n;i++) bl[i]=(i-1)/blo+1;
    sort(q+1, q+1+m);
    for(int i=1, l=1, r=0;i<=m;i++)
    {
        while(l<q[i].l) update(l++, -1);
        while(l>q[i].l) update(--l, 1);
        while(r<q[i].r) update(++r, 1);
        while(r>q[i].r) update(r--, -1);
        ...//更新答案
    }
}

【例题】

  例1:bzoj2038: [2009国家集训队]小Z的袜子(hose)

  裸题了,求区间的每种颜色出现次数,然后随便算一下贡献即可。

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=500010, inf=1e9;
struct poi{int l, r, pos;}q[maxn];
int n, m;
int a[maxn], bl[maxn], cnt[maxn];
ll fz, ansfz[maxn], ansfm[maxn];
inline void read(int &k)
{
    int f=1; k=0; char c=getchar();
    while(c<‘0‘ || c>‘9‘) c==‘-‘&&(f=-1), c=getchar();
    while(c<=‘9‘ && c>=‘0‘) k=k*10+c-‘0‘, c=getchar();
    k*=f;
}
bool operator < (poi a, poi b)
{return bl[a.l]<bl[b.l] || (bl[a.l]==bl[b.l] && ((bl[a.l]&1)?a.r<b.r:a.r>b.r));}
inline void update(int x, int delta)
{
    fz-=1ll*cnt[a[x]]*(cnt[a[x]]-1);
    cnt[a[x]]+=delta;
    fz+=1ll*cnt[a[x]]*(cnt[a[x]]-1);
}
ll gcd(ll a, ll b){return b?gcd(b, a%b):a;}
int main()
{
    read(n); read(m); int blo=sqrt(n);
    for(int i=1;i<=n;i++) read(a[i]);
    for(int i=1;i<=m;i++) read(q[i].l), read(q[i].r), q[i].pos=i;
    for(int i=1;i<=n;i++) bl[i]=(i-1)/blo+1;
    sort(q+1, q+1+m);
    for(int i=1, l=1, r=0;i<=m;i++)
    {
        while(l<q[i].l) update(l++, -1);
        while(l>q[i].l) update(--l, 1);
        while(r<q[i].r) update(++r, 1);
        while(r>q[i].r) update(r--, -1);
        int len=q[i].r-q[i].l+1;
        int d=gcd(fz, 1ll*len*(len-1));
        ansfz[q[i].pos]=fz/d; ansfm[q[i].pos]=1ll*len*(len-1)/d;
    }
    for(int i=1;i<=m;i++) printf("%lld/%lld\n", ansfz[i], ansfm[i]?ansfm[i]:1);
}

  例2:bzoj[Ahoi2013]作业

  加个权值树状数组维护一下每种权值的出现次数以及种类即可。

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=1000010, inf=1e9;
struct poi{int l, r, a, b, pos;}q[maxn];
int n, m;
int a[maxn], bl[maxn], tree[2][maxn], ans1[maxn], ans2[maxn], cnt[maxn];
inline void read(int &k)
{
    int f=1; k=0; char c=getchar();
    while(c<‘0‘ || c>‘9‘) c==‘-‘&&(f=-1), c=getchar();
    while(c<=‘9‘ && c>=‘0‘) k=k*10+c-‘0‘, c=getchar();
    k*=f;
}
bool operator < (poi a, poi b)
{return bl[a.l]<bl[b.l] || (bl[a.l]==bl[b.l] && ((bl[a.l]&1)?a.r<b.r:a.r>b.r));}
inline void add(int ty, int x, int delta){for(;x<=n;x+=x&-x) tree[ty][x]+=delta;}
inline int query(int ty, int x){int sum=0; for(;x;x-=x&-x) sum+=tree[ty][x]; return sum;}
inline void update(int x, int delta)
{
    add(0, a[x], delta);
    if(!cnt[a[x]] && delta==1) add(1, a[x], 1);
    cnt[a[x]]+=delta;
    if(!cnt[a[x]] && delta==-1) add(1, a[x], -1);
}
int main()
{
    read(n); read(m); int blo=sqrt(n);
    for(int i=1;i<=n;i++) bl[i]=(i-1)/blo+1;
    for(int i=1;i<=n;i++) read(a[i]);
    for(int i=1;i<=m;i++) read(q[i].l), read(q[i].r), read(q[i].a), read(q[i].b), q[i].pos=i;
    sort(q+1, q+1+m);
    for(int i=1, l=1, r=0;i<=m;i++)
    {
        while(l<q[i].l) update(l++, -1);
        while(l>q[i].l) update(--l, 1);
        while(r<q[i].r) update(++r, 1);
        while(r>q[i].r) update(r--, -1);
        ans1[q[i].pos]=query(0, q[i].b)-query(0, q[i].a-1);
        ans2[q[i].pos]=query(1, q[i].b)-query(1, q[i].a-1);
    }
    for(int i=1;i<=m;i++) printf("%d %d\n", ans1[i], ans2[i]);
}

  例3

原文地址:https://www.cnblogs.com/Sakits/p/8387559.html

时间: 2024-12-10 04:25:04

【算法】莫队算法初探的相关文章

莫队算法---基础知识介绍(转载)

莫队算法 莫队算法可用于解决一类可离线且在得到区间[l,r][l,r]的答案后,能在O(1)O(1)或O(log2n)O(log2?n)得到区间[l,r+1][l,r+1]或[l−1,r][l−1,r]的答案的问题 先看这样一个问题: 给出n个数字,m次询问,每次询问在区间[li,ri][li,ri]之间任选两个数字相等的概率是多少.(n,q<=50000)(小z的袜子) 在区间[l,r][l,r]中,这个概率是: ∑vi=1C(2,f(i))C(2,r−l+1)∑i=1vC(2,f(i))C(

BZOJ 2038: [2009国家集训队]小Z的袜子(hose) [莫队算法]【学习笔记】

2038: [2009国家集训队]小Z的袜子(hose) Time Limit: 20 Sec  Memory Limit: 259 MBSubmit: 7687  Solved: 3516[Submit][Status][Discuss] Description 作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿.终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命……具体来说,小Z把这N只袜子从1到N编号,然后从编号L到R(L 尽管小Z并不在意两只

信心题--FUOJ2226(莫队算法)

http://acm.fzu.edu.cn/problem.php?pid=2226 信心题,还说是信心题,题目给的真好.但是一点都不像信心题. 又是一个新的算法,莫队算法 莫队算法是一个用数组就可以轻易实现的神奇数据结构,可以处理一类无修改的离线区间查询问题(PS:暂时还没有遇到莫队解决更新区间查询的问题) 莫队算法可以在O(1),实现[l, r]到[l, r±1] / [l±1, r]的转移,然后我们就可以对所有查询分sqrt(n)块,把每个查询所在的块号当做第一关键字,右端点作为第二关键字

莫队算法(离线)

何谓莫队算法 莫队算法是莫涛队长发明的,为表示对他的尊敬,故称这种算法叫莫队. 适用范围 一种处理序列操作的离线算法,适用范围广,复杂度一般带根号. 莫队算法的思路 假设题目不涉及修改操作. 将所有操作离线,将所有操作进行二元组排序,第一维是左端点所在块的编号,第二维是右端点. 排序后,按照顺序处理询问,维护一个双端队列,同时维护队列内区间的答案,每次从$[L,R]$的答案,扩展到$[L-1,R]$ $[L,R-1]$ $[L+1,R]$ $[L,R+1]$的答案,最终得到当此询问区间的答案.

莫队算法&amp;#183;初探总结

莫队算法分那么几类: 普通序列 带修改 树上 回滚 支持在线 其实上述的类型还可以组合起来(非常的毒瘤). 个人理解莫队算法的精髓在于如何利用暴力将答案再合理的时间和空间内跑出来.说白了: \[莫队算法=一种很牛逼的自定义排序+分块处理+暴力 \] 首先要理解自定义排序,这个排序之后整个序列可以最快地处理所有的询问(这里暂时不谈第五类问题(支持在线),这里认为莫队是只能离线处理问题的,必须先把所有的问题都离线下来).怎么为之快,快要看左端点移动的总距离+右端点移动的总距离最小.那么一般用块的奇偶

莫队算法

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]. 详细的莫队可以百度学一