• 普通莫队算法


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

    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 ",anss[i]);
      }
      return 0;
    }
  • 相关阅读:
    ionic入门之AngularJS扩展基本布局
    ionic入门之AngularJS扩展(一)
    test
    面试题小整理
    使用Code first 进行更新数据库结构(数据迁移)
    SQL模糊查询与删除多条语句复习
    GridView 根据要求显示指定值
    个人工作记录---工作中遇到的sql查询语句解析
    数据库,inner join,left join right join 的区别
    利用set实现去重
  • 原文地址:https://www.cnblogs.com/wushengyang/p/11248557.html
Copyright © 2020-2023  润新知