zoukankan      html  css  js  c++  java
  • 莫队算法---基础知识介绍(转载)

    莫队算法

    莫队算法可用于解决一类可离线且在得到区间[l,r][l,r]的答案后,能在O(1)O(1)或O(log2n)O(log2⁡n)得到区间[l,r+1][l,r+1]或[l1,r][l−1,r]的答案的问题

    先看这样一个问题:

    给出n个数字,m次询问,每次询问在区间[li,ri][li,ri]之间任选两个数字相等的概率是多少。(n,q<=50000)(小z的袜子)

    在区间[l,r][l,r]中,这个概率是:

    vi=1C(2,f(i))C(2,rl+1)∑i=1vC(2,f(i))C(2,r−l+1)
    (v表示数字值,f(i)表示数字i在区间内出现的次数)

    由于没有加和性质,传统的线段树什么的完全派不上用场了呢!

    考虑分子,因为C(2,x)=x2x2C(2,x)=x2−x2,所以分子=vi=1f(i)2vi=1f(i)2∑i=1vf(i)2−∑i=1vf(i)2
    显然 vi=1f(i)=rl+1∑i=1vf(i)=r−l+1

    若得知区间[l,r][l,r]的答案怎么求区间[l,r+1][l,r+1]的答案呢?仔细想想。恩,有了。区间[l,r+1][l,r+1]与区间[l,r][l,r]相比只多了一个元素Z,这种改动是很小的,那么前式中分子的值S=S0f(Z)2+(f(Z)+1)21=S0+2f(Z)S=S0−f(Z)2+(f(Z)+1)2−1=S0+2∗f(Z),同时++f(z),恩,O(1)O(1)的。这样的话,在处理下一个询问[li,ri][li,ri]时,复杂度就是O(|rri|+|lli|)O(|r−ri|+|l−li|)的。同样的方法,也可以在O(1)O(1)内求出[l1,r][l−1,r],[l+1,r][l+1,r],[l,r1][l,r−1]。这样的方法对于随机数据表现是很好的,但也不难给出故意卡你的数据。

    这时,就需要莫队算法来撑腰了,这也是莫队算法优化的精髓。

    注意到,每个区间可以抽象成平面中的点,每次转移的花费都相当与从某点到另一点的曼哈顿距离的长度。恩,所以呢?

    所以我们花费的便是这些平面中的点联通的曼哈顿距离。平面点的曼哈顿最小生成树!

    对!但平面点的曼哈顿最小生成树怎么求呢?枚举两两点连接O(n2)O(n2),毫无意义。其实平面点的曼哈顿最小生成树有基于平面区域划分的O(nlog2n)O(nlog2n)的求法,但我们有更简洁的方法。对,分块!

    确实,利用分块,我们可以实现O(nn√)O(nn)的时间复杂度。虽然求解平面点的曼哈顿最小生成树是O(nlog2n)O(nlog2n)的,但根据莫队论文中的证明,用到这里时,仍然是O(nn√)O(nn),只不过常数小一些罢了。

    分块的做法:
    x=(√n)x=(n),以[1,x],[x+1,2x],[2x+1,3x]...[1,x],[x+1,2x],[2x+1,3x]...分块
    用pos数组维护端点i在第pos[i]块中,然后就搞呗。

    这样搞:

    1):排序,以左段点所在的块为第一关键字,以右端点为第二关键字

    2):从左往右处理询问(离线)

    3):不断调整l,r的位置并同时修改

    时间复杂度证明:

    右端点移动:
    首先我们考虑一个块里面的转移情况
    由于一个块里面的询问都按右端点排序
    所以我们右端点在一个块里面最多移动n次
    有 O(n√)O(n)个块,那么同一个块内的右端点移动最多就是O(nn√)O(nn)
    然后考虑从一个块到另一个块导致的右端点变化
    最坏情况,右端点由n到1,那么移动n次
    有 O(n√)O(n)个块
    那么从一个块到另一个块的事件只会发生O(n√)O(n)次……
    所以这种右端点移动的次数也是O(nn√)O(nn)次
    没有别的事件导致右端点移动了
    左端点移动:
    同一个块里面,由于左端点都在一个长度为O(n√)O(n)的区间里面
    所以在同一块里面移动一次,左端点最多变化O(n√)O(n)
    总共有n个询问……
    所以同一块里面的移动最多n次
    那么同一个块里面的左端点变化最多是O(nn√)O(nn)的
    考虑跨越块
    每由第i个块到第i+1个块,左端点最坏加上O(n√)O(n)
    总共能加上O(n√)O(n)次
    所以跨越块导致的左端点移动是O(n)O(n)的
    综上,分块做法是O(nn√)O(n∗n)。

    总结

    莫队算法在解决离线区间询问几乎是无敌的。
    恩,几乎只要能离线,用分块的莫队算法都能取得一个令人满意的的解法。
    所以就有很多扩展(解决线段树等数据结构由于需要区间加和性而不能解决的问题),如区间众数,平均数什么的。
    恩。棒!

    附:
    [BZOJ]2038 小Z的袜子 分块 莫队算法

    #include <cstdio>
    #include <cmath>
    #include <algorithm>
    
    using namespace std;
    
    const int maxn = 50000 + 500;
    typedef long long LL;
    
    LL gcd(LL a,LL b)
    {
        return (b==0)?a:gcd(b,a%b);
    }
    
    int pos[maxn];
    int col[maxn];
    int f[maxn];
    int n,m;
    
    struct Query
    {
        int l,r,id;
        LL a,b;
        friend bool operator < (const Query &R,const Query &T)
        {
            return pos[R.l]<pos[T.l] || (pos[R.l]==pos[T.l] && R.r<T.r);
        }
        void modify()
        {
            LL k=gcd(a,b);
            a/=k,b/=k;
        }
    }Q[maxn];
    bool cmp_id(const Query &a,const Query &b)
    {
        return a.id<b.id;
    }
    
    void init()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;++i)
            scanf("%d",&col[i]);
        int limit=(int)sqrt((double)n+0.5);
        for(int i=1;i<=n;++i)
            pos[i]=(i-1)/limit+1;//左端点分块
        for(int i=1;i<=m;++i)
        {
            scanf("%d%d",&Q[i].l,&Q[i].r);
            Q[i].id=i;
        }
        sort(Q+1,Q+m+1);
    }
    
    void modify(int p,LL &ans,int add)
    {
        ans=ans+2*add*f[col[p]]+1;
        f[col[p]]+=add;
    }
    
    void solve()
    {
        LL ans=0;
        int l=1,r=0;
        for(int i=1;i<=m;++i)
        {
            if(r<Q[i].r)
            {
                for(r=r+1;r<Q[i].r;++r)
                    modify(r,ans,1);
                modify(r,ans,1);
            }
            if(Q[i].l<l)
            {
                for(l=l-1;Q[i].l<l;--l)
                    modify(l,ans,1);
                modify(l,ans,1);
            }
            if(Q[i].r<r)
                for(;Q[i].r<r;--r)
                    modify(r,ans,-1);
            if(l<Q[i].l)
                for(;l<Q[i].l;++l)
                    modify(l,ans,-1);
            if(Q[i].l==Q[i].r)
            {
                Q[i].a=0,Q[i].b=1;
                continue;
            }
            Q[i].a=ans-(Q[i].r-Q[i].l+1),Q[i].b=(LL)(Q[i].r-Q[i].l+1)*(Q[i].r-Q[i].l);
            Q[i].modify();
        }
        sort(Q+1,Q+m+1,cmp_id);
        for(int i=1;i<=m;++i)
            printf("%lld/%lld
    ",Q[i].a,Q[i].b);
    }
    
    int main()
    {
        init();
        solve();
    
        return 0;
    }

    Refrence:
    http://foreseeable97.logdown.com/posts/158522-233333

    http://ydcydcy1.blog.163.com/blog/static/21608904020134411543898/

    http://vawait.com/manhattanmst/

    http://blog.csdn.net/huzecong/article/details/8576908

  • 相关阅读:
    数据类型转换:高级向低级转换可能出现的问题和取得不同精度的方法
    Xen Server虚拟机数据恢复的方法和数据恢复过程
    VMware虚拟机误删除vmdk文件后如何恢复?
    MSSQL 2000 错误823恢复
    服务器存储误操作导致数据丢失的恢复过程
    我从业11年来遇到的最奇葩的raid0+1数据恢复经历
    Raid 5数据恢复原理以及raid 5数据恢复实际操作案例
    服务器数据恢复_Linux网站服务器故障数据恢复案例
    Raid5两块硬盘掉线可以恢复数据吗_raid数据恢复案例分享
    V7000存储数据恢复_底层结构原理拆解及Mdisk磁盘掉线数据恢复方法
  • 原文地址:https://www.cnblogs.com/chen9510/p/5851064.html
Copyright © 2011-2022 走看看