算法莫队算法初探

Posted Sakits

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法莫队算法初探相关的知识,希望对你有一定的参考价值。

【算法介绍】

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

  当我们得到$[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);
        ...//更新答案 
    }
}
View Code

【例题】

  例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);
}
View Code

 

  例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]);
}
View Code

 

  例3

以上是关于算法莫队算法初探的主要内容,如果未能解决你的问题,请参考以下文章

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

(莫队算法)两题莫队算法统计数量的入门题

CSU 1515 Sequence (莫队算法)

莫队小结

算法笔记莫队算法(基础莫队,带修莫队,回滚莫队,树上莫队,二次离线莫队)

莫队算法~讲解