优雅的区间暴力莫队算法

莫队算法是优雅的暴力而不是什么神奇的数据结构。

只要是区间离线可以算的莫队几乎都可以达到O(n sqrt (n) )的时间复杂度

为什么叫莫队算法呢?据说这是2010年国家集训队的莫涛在作业里提到了这个方法。

由于莫涛经常打比赛做队长,大家都叫他莫队,该算法也被称为莫队算法。

 

一个例题:给出数组n个元素a[]和Q组询问[L,R]求[L,R]中那些数字出现了T次?

Subtask1:对于99%的数据 1<=L<R<=1000 ,1<=n<=1000,1<=Q<=100

Subtask1.5:对于99.999%的数据 1<=L<R<=100000 ,1<=n<=100000,1<=Q<=10000且数据保证随机

Subtask2:对于100%的数据 1<=L<R<=100000 ,1<=n<=100000,1<=Q<=10000且数据保证一定程度上的随机

Subtask1怎么做?

暴力?怎么暴力:

  • 我们用一个cnt[ ]数组记录每种元素,用桶排的思想,枚举区间,
  • 每遇到一个元素对应的桶++,然后暴力一遍所有的桶,等于1的我们ans就++,
  • 这样统计不同的个数,看看是不是等于L到R,
  • 然后再清空桶和ans,做下一组询问。

好吧这样你就学会了99%的莫队算法。

Subtask1.5 优化暴力

显然处理询问的次序和时间复杂度有有关系,如果确定合理的次序这样也不难成为一个好方法。。

剩下1%就是怎么确定搞询问的次序,一种可行的方法是让L和R恰好单增,让前面可以用的东西尽可能多

但是这样的表现不好。特别是面对精心设计的数据,这样方法(按照L排序R排序)表现得很差。

 

/*
举个栗子,有6个询问如下:(1, 100), (2, 2), (3, 99), (4, 4), (5, 102), (6, 7)。

这个数据已经按照左端点排序了。用上述方法处理时,左端点会移动6次,右端点会移动移动98+97+95+98+95=483次。

其实我们稍微改变一下询问处理的顺序就能做得更好:(2, 2), (4, 4), (6, 7), (5, 102), (3, 99), (1, 100)。

左端点移动次数为2+2+1+2+2=9次,比原来稍多。右端点移动次数为2+3+95+3+1=104,右端点的移动次数大大降低了。
*/

首先:考虑我们有两个指针。一个叫做curL,另一个叫curR。他们对应的是所指的数的标号。这样我们可以利用这两个指针进行移动,每次只能向左或向右移动一步。移动的复杂度是O(1)。

我们现在处理了curL—curR区间内的数据,现在左右移动,比如curL到curL-1,只需要更新上一个新的3,即curL-1。

那么curL到curL+1,我们只需要去除掉当前curL的值。因为curL+1是已经维护好了的。

curR同理,但是要注意方向哦!curR到curR+1是更新,curR到cur-1是去除。

我们先计算一个区间 [curL curR] 的answer,这样的话,我们就可以用O(1)转移到 [curL-1 curR] [curL+1 curR] [curL curR+1] [curL curR-1] 上来并且求出这些区间的answer。

我们利用curL和curR,就可以移动到我们所需要求的[L R]上啦~

Subtask2: 怎么优化1.5?——分块

我们把所有的元素分成多个块(即分块)。分了块跑的会更快。再按照右端点从小到大,左端点块编号相同按右端点从小到大。

 

程序实现:

# include <bits/stdc++.h>
using namespace std;
const int MAXN=100005;
struct rec{
    int p,bl,l,r;
}q[MAXN];
int n,m,bo[MAXN],a[MAXN],answer;
int ans[MAXN];
bool cmp(rec a,rec b)
{
    return (a.bl<b.bl||(a.bl==b.bl&&a.r<b.r));
}
void add(int pos){

//将a[pos]加入并更新answer 

}
void del(int pos){

//将a[pos]去除并更新answer 

}
int main()
{
    scanf("%d%d",&n,&m); int block=sqrt(n);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    for (int i=1;i<=m;i++) {
        scanf("%d%d",&q[i].l,&q[i].r);
        q[i].p=i; q[i].bl=(q[i].l-1)/block+1;
    }
    sort(q+1,q+1+m,cmp);
    int curL=0,curR=0; 
    for (int i=1;i<=m;i++) {
        int L=q[i].l,R=q[i].r;
        while (curL>L) add(--curL);
        while (curL<L) del(curL++);
        while (curR>R) del(curR--);
        while (curR<R) add(++curR);
        ans[q[i].p]=answer;
    }
    for (int i=1;i<=m;i++)
     printf("%d\n",ans[i]);
    return 0;
}

 

posted @ 2018-09-25 19:51  ljc20020730  阅读(187)  评论(0编辑  收藏  举报