zoukankan      html  css  js  c++  java
  • 优雅的区间暴力莫队算法

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

    只要是区间离线可以算的莫队几乎都可以达到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
    ",ans[i]);
        return 0;
    }
  • 相关阅读:
    我所理解的MVC
    关于开发文化的思考
    使用纯C语言开始win32 sdk编程
    谈谈编译原理和其在WEB开发中的应用1
    最简单的win32程序(c语言实现)
    关于Javascript的一些心得与记忆
    Cvv.WebUtility 我的MVC框架介绍
    发现了个不错的图片网站
    轻轻松松做开发--目前网站开发的流行原素
    我的模板分析引擎类PHP的.net开发方法标签设计篇
  • 原文地址:https://www.cnblogs.com/ljc20020730/p/9703027.html
Copyright © 2011-2022 走看看