莫队算法---基础知识介绍(转载)

莫队算法

莫队算法可用于解决一类可离线且在得到区间[l,r][l,r]的答案后,能在O(1)O(1)或O(log2n)O(log2?n)得到区间[l,r+1][l,r+1]或[l−1,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,r−l+1)∑i=1vC(2,f(i))C(2,r−l+1)

(v表示数字值,f(i)表示数字i在区间内出现的次数)

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

考虑分子,因为C(2,x)=x2−x2C(2,x)=x2−x2,所以分子=∑vi=1f(i)2−∑vi=1f(i)2∑i=1vf(i)2−∑i=1vf(i)2
显然 ∑vi=1f(i)=r−l+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=S0−f(Z)2+(f(Z)+1)2−1=S0+2∗f(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(|r−ri|+|l−li|)O(|r−ri|+|l−li|)的。同样的方法,也可以在O(1)O(1)内求出[l−1,r][l−1,r],[l+1,r][l+1,r],[l,r−1][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(n∗n√)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\n",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

时间: 2024-10-13 07:29:08

莫队算法---基础知识介绍(转载)的相关文章

[hdu5213]容斥原理+莫队算法

题意:给一个序列a,以及K,有Q个询问,每个询问四个数,L,R,U,V, 求L<=i<=R,U<=j<=V,a[i]+a[j]=K的(i, j)对数(题目保证了L <= R < U <= V). 思路:首先用容斥原理把询问变为i,j同区间,记f(A,B)为答案,'+'为区间的并,A=[L,R],B=[U,V],C=[u+1,v-1],则f(A,B) = f(A+B+C,A+B+C)+f(C,C)-f(A+C,A+C)-f(B+C,B+C).令g(L,R) = f(

hdu5212 Code 莫队算法

这道题需要一些莫队算法的知识 定义记号f(A,B)表示询问区间A,B时的答案 用记号+表示集合的并 利用莫队算法我们可以计算出任意f(A,A)的值 不妨假设A=[l1,r1],B=[l2,r2],C=[r1+1,l2?1] 容易知道f(A,B)=f(A+B+C,A+B+C)+f(C,C)?f(A+C,A+C)?f(C+B,C+B) 因此一个询问被拆成四个可以用莫队算法做的询问 总的时间复杂度为O(msqrt(n)) (以上是官方题解) 代码: #include<iostream> #inclu

(普通的)莫队算法简单介绍

莫队算法(由莫涛发明的)是一种离线的暴力算法(至少我这么认为).使用莫队算法的条件是,知道一个区间[l, r]的结果,那么也可以快速知道[l + 1, r],[l - 1, r], [l, r - 1], [l, r + 1]这四个区间的结果.于是可以想到,直接通过这样转移来解决一些问题.当然有些出题人机智,故意卡这种暴力,让你从头跑到尾然后从尾跑到头,于是时间复杂度高达O(n2) 而莫队算法就是通过改变处理询问的顺序来降低时间复杂度. 比如说现在知道一个区间[l1, r1],又要转移到[l2,

[转载] 莫队算法

FROM: http://www.cnblogs.com/CsOH/p/5904430.html 问题:有n个数组成一个序列,有m个形如询问L, R的询问,每次询问需要回答区间内至少出现2次的数有哪些. 朴素的解法需要读取O(nm)次数.如果数据范围小,可以用数组,时间复杂度为O(nm).如果使用STL的Map来保存出现的次数,则需要O(nmlogn)的复杂度.有没有更快的方法呢? 注意到询问并没有强制在线,因此我们可以使用离线方法.注意到一点,如果我们有计算完[L, R]时的"中间变量&quo

「知识学习&amp;日常训练」莫队算法(一)(Codeforce Round #340 Div.2 E)

题意 已知一个长度为\(n\)的整数数列\(a[1],a[2],-,a[n]\),给定查询参数\(l,r\),问\([l,r]\)内,有多少连续子段满足异或和等于\(k\). 也就是说,对于所有的\(x,y (l\le x\le y\le r)\),能够满足\(a[x]\oplus a[x+1]\oplus ...\oplus a[y]=k\)的\((x,y)\)有多少组. 分析 对于这种离线区间的查询问题(不涉及对区间的更改),我们可以使用莫队算法解决.这类问题是什么类型?对于序列上的区间询问

# 莫队算法小结(待更新)

目录 莫队算法小结(待更新) 简单介绍 基础莫队 带修莫队 树上莫队 莫队算法小结(待更新) 简单介绍 博客安利: OI Wiki 大米饼 解决一类离线区间查询问题,分块思想,时间复杂度\(O(n\sqrt n)\) 排序 读入的时候对整个数组进行分块,块大小一般使用\(\sqrt n\),对询问操作排序的时候,先以块号为第一关键字,\(r\)为第二关键字,从小到大排序,然后逐个遍历 离线后将询问排序,顺序处理每个询问,暴力从上一个区间的答案转移到下一个区间的答案(通过两个指针的\(++\)和\

莫队算法及其应用

在写这篇博客之前,我最想做的一件事就是:ORZ莫队%%%%%%%%. 说明:ceil(x)表示x向上取整,sqrt(x)表示对x开算数平方根. 一.莫队算法简介 莫队算法是一种暴力算法,真的很暴力,但速度很快,属于速度快的暴力.它的基本思想就是分块.关于分块的介绍建议参考hzwer的博客,然后%%%%hzw.莫队算法主要用于解决一类离线查询的问题,和线段树处理的问题是一样的,但处理的是两个不同的方面,当由[L,R]转移到[L',R']的时间为O(|L'-L|+|R'-R|)时适宜使用莫队算法.这

带修改的莫队算法学习小记

简介 莫涛大神创造出的离线询问算法的带修改版. 算法基础:需要掌握莫队算法,会打暴搜(暴力). 一个叫莫的双端队列. 只支持单点修改 操作方法 普通的不带修改的莫队算法要把每个询问带上两个关键字排序,现在待修改的莫队算法要带上三个关键字排序. 初始操作 fo(i,1,m) { scanf("%s%d%d",s,&k,&l); if (s[0]=='Q')a[++tot].l=k,a[tot].r=l,a[tot].x=num,a[tot].p=tot; else d[+

数据结构(莫队算法):HEOI2012 采花

[题目描述] 萧薰儿是古国的公主,平时的一大爱好是采花. 今天天气晴朗,阳光明媚,公主清晨便去了皇宫中新建的花园采花.花园足够大,容纳了n朵花,花有c种颜色(用整数1-c表示),且花是排成一排的,以便于公主采花.公主每次采花后会统计采到的花的颜色数,颜色数越多她会越高兴!同时,她有一癖好,她不允许最后自己采到的花中,某一颜色的花只有一朵.为此,公主每采一朵花,要么此前已采到此颜色的花,要么有相当正确的直觉告诉她,她必能再次采到此颜色的花.由于时间关系,公主只能走过花园连续的一段进行采花,便让女仆