普通莫队算法

以前一直认为莫队算法很难,但是现在看了下,发现如果只是普通的莫队算法还是比较容易理解的。

https://www.luogu.org/blog/codesonic/Mosalgorithm  这篇洛谷的博客讲的很详细了。

莫队算法一般用于询问区间中,比如对于一个数列1~n在10000左右,然后又有m个询问,m也是10000的级别,每个询问问你在1~n的某个区间的什么什么满足条件的个数之类的。

那么显而易见如果用暴力的话,那么极限情况下的复杂度就是O(mn),再加上如果里面有什么排序之类的话,就是更得O(mnlogn)的复杂度了,那么肯定会tle的。

所以这个时候就需要莫对算法了。

那么莫队算法是什么。

其实莫队的思想就是离线查询。相当于在查询前,先把所有的查询的答案给储存下来,到时候再直接输出所有答案。

那么如何查询所需区间的答案呢,直接for循环肯定不行。这个时候就引入了一种暴力的思想,比如首先定义一个 l=0 和 r=0,代表初始的查询的指针的位置,然后将 l 和 r 与所要查询的区间 [ql,qr]来对比一下,如果 l<ql 那么就右移,然后判断这种变化会使区间的变量少了多少种,注意这种减要减去当前自己所在位置的情况 所以一般是 del(l++),如果是 l>ql 那么就要左移,这时候情况就会增加,但是当前位置的情况以及算入进去了,就需要 add(--l),对于r的变化也是同理。

以洛谷的板子题目为例 https://www.luogu.org/problem/P2709

void add(int x){
    sum[c[x]]++;
    ans+=2*sum[c[x]]-1; 
//一开始ans存的是sum[c[x]]^2,然后要求(sum[c[x]]+1)^2,所以相减一下,
//注意这里你sum[c[x]]的值先加了个1,所以相当于多加了2,要减掉2
}
void del(int x){
    sum[c[x]]--;
    ans-=2*sum[c[x]]+1;
}

以及在移动的时候的变化:

    for(ll i=1;i<=m;i++){
        ll ql=q[i].l,qr=q[i].r;
        while(l<ql) del(l++);
        while(l>ql) add(--l);
        while(r<qr) add(++r);
        while(r>qr) del(r--);
        anss[q[i].id]=ans;
    }

虽然这样的移动每次都是O(1) ,但是每次询问的移动次数最多依然是n次,时间复杂度依然是O(nm),那么怎么加速呢?由于每次时间的耗时在与移动次数上,所以我们尽可能的只要减少移动次数就行,这时候就可以将询问先储存下来,再按照某种方式排序,让他减少移动的次数,就会变快一点。

关于排序的方法以及复杂度的证明,接下来就直接上图了,其实大概知道排序的写法就行。

然后就是排序的两种写法了:

bool cmp(node x,node y){
    return (x.r/block)==(y.r/block)?x.l<y.l:x.r<y.r;
}

奇偶性排序是这样的:

bool cmp(node x,node y){
    return (x.l/block)^(y.l/block)?x.l<y.l:(((x.l/block)&1)?x.r<y.r:x.r>y.r);
}

这样能快是因为指针移到右边后不用再跳回左边,而跳回左边后处理下一个块又要跳回右边,这样就能减少一半操作,理论上能快一倍

注意:分块时块的大小不是固定的,要根据题目具体分析,分析的过程如上面分析m极大时的复杂度。

 然后就是洛谷模板题的代码了:

#include<bits/stdc++.h>
using namespace std;
#define ll long long int 
const int maxn=50005;
ll c[maxn],sum[maxn];
struct node{
    ll l,r,id;
}q[maxn];

ll anss[maxn];
ll block,ans=0;
bool cmp(node x,node y){
    return (x.l/block)^(y.l/block)?x.l<y.l:(((x.l/block)&1)?x.r<y.r:x.r>y.r);
}
void add(int x){ sum[c[x]]++; ans+=2*sum[c[x]]-1; }//一开始ans存的是sum[c[x]]^2,然后要求(sum[c[x]]+1)^2,所以相减一下, //注意这里你sum[c[x]]的值先加了个1,所以相当于多加了2,要减掉2
void del(int x){ sum[c[x]]--; ans-=2*sum[c[x]]+1; }
int main(){
  ll n,m,k;
  cin
>>n>>m>>k;
  block
=sqrt(n);
  for(ll i=1;i<=n;i++){
    cin
>>c[i];
  }
  for(ll i=1;i<=m;i++){
    cin
>>q[i].l>>q[i].r;
    q[i].id
=i;
  }
  sort(q
+1,q+m+1,cmp);
  int l=1,r=0;
  for(ll i=1;i<=m;i++){
    ll ql
=q[i].l,qr=q[i].r;
    while(l<ql) del(l++);
    while(l>ql) add(--l);
    while(r<qr) add(++r);
    while(r>qr) del(r--);
    anss[q[i].id]
=ans;
  }
  
for(ll i=1;i<=m;i++){
    printf(
"%lld\n",anss[i]);
  }
  return 0;
}

 

posted @ 2019-07-26 10:06  wushuyng  阅读(235)  评论(0编辑  收藏  举报