解析和实现

摘要:

       莫队算法是一个对于区间、树或其他结构离线(在线)维护的算法,此算法基于一些基本算法,例如暴力维护,树状数组,分块,最小曼哈顿距离生成树,对其进行揉合从而产生的一个简单易懂且短小好写的算法。此算法在很多情况下可以很轻松的切掉一些复杂而且难写的数据结构问题。

关键词:

程序设计、算法、算法优化,暴力算法,分块算法,最小曼哈顿距离生成树。

背景:

众所周知,在OI竞赛、软件的设计中都会要求我们去处理各种各样的棘手的问题,而这些问题之中,有一大类就是维护问题:比如说对于一个序列的维护,对于棵二叉或者多叉树的维护……这些问题往往会需要我们去使用一个或多个高端的数据结构复合来完美解决,通常题目的代码十分冗长而且出错可能性十分大,是广大OIer、Acmer、Coder所害怕的题目。那么有没有一种方法可以既简单又快捷的解决这类问题(这类问题中的一大部分)呢?莫队算法就诞生辣!

理论1:

序列莫队:我们现在有一个长为n的静态的序列,对于序列,我们有m次查询,我们要动态查询l到r之间大于a小于b的数的个数以及种类。遇到了这个问题我们通常需要使用书套树的数据结构,即一颗以自平衡二叉查找树为节点的线段树(时间复杂度大约是O(mlognlogn)),而且由于空间限制,我们还必须动态创建线段树的节点,这样一来十分难写,一些大约要个400-500行,调试起来也很困难。这时候我们来考虑暴力算法,如果暴力的处理题目中的问题那么复杂度是多少呢?这个不难计算,对于每个询问我们都要O(n)的时间处理,一共有m个询问,那么暴力处理的复杂度就是O(nm)的,明显处理问题花费的时间我们是不能接受的。这是我们想到可以交换询问和询问之间的先后次序,这样每次询问在前一次询问的基础上转移就可以节省一些时间了。

但是如何重新排列询问之间的顺序是一个问题。我们需要进行一些理论分析。我们再上一个询问的基础上暴力地维护一个询问(假设上一个询问询问区间为[l0,r0],这个询问区间为[l,r]),那么我们所谓的暴力维护就是先把现有答案的右边界从r0移动到r,再把左边界从l0移动到l,那么我们的总花费是O(|l-l0|+|r-r0|)。仔细看一看,没错,这就是我们的曼哈顿距离的计算公式,有了这个思路,我们就可以从图形的角度来思考了,对于一个询问[l,r]我们可以将它映射为平面上在(l,r)位置的点,那么两个询问之间转移的代价就是询问所对应的点之间的曼哈顿距离。有了这一个结论,我们便想到可以用最小曼哈顿生成树来处理询问的顺序。由此莫队算法便诞生啦!莫队算法就是先将询问抽象成平面上的点,然后进行一边最小曼哈顿距离生成树,然后按照生成树的顺序来处理询问,这样的算法复杂度大约是O(mSqrt(n))的。如此,问题便简单了许多。

但是由于最小曼哈顿距离生成树也不是那么的好写,所以莫队算法还能再简单一点么?我们思考是否可以用一个简单而暴力的算法代替莫队算法呢。很快便能想到分块算法。我们可以使用分块算法来处理询问之间的次序问题。再去看那个询问对应的点所在的平面,我们找到它的X轴,我们把X轴平均分割成r分,然后我们把在一个块内的询问统一先处理,不在一个块内的询问我们按照左端点升序右端点升序排序依次处理。这样做有什么好处呢?对于m干个询问,如果在一个块里面,那么处理这些询问花费的复杂度是O(n/r*n*m),如果有两个询问不在一个同一个块里面,按照我们之前的排序规则,我们把左区间和右区间在块之间移动的次数最多为r*(n/r)*r次,那么我们的复杂度就是O(r*(n/r)*r)次,经过简单的数学分析,我们可以发现r=Sqrt(n)是时间复杂度最低为O(nSqrt(n))次,是可以接受的时间复杂度。这样我们的莫队算法就又简单有强大了。但是在另一些情况下,题目会无耻的限定我们可以使用的空间(一般不会,因为这样高级数据结构的复合也难以解决这样的问题了)。那么如果空间被限定了,我们应该如何解决问题呢?其实很简单, 还记得我们之前的r么?我们为了求的时间复杂度最小令r=Sqrt(n),如果我们令r=n ^ (2 / 3),那么便是一个时间复杂度和空间复杂度较为平衡的情况,这样可以很好的解决问题。

例题:

输入数据首先输入两个整数N,Q,分别代表序列的长度和询问的个数。这两个数字将单独占据一行并用一个空格分开。输入数据的第二行包含了N个由一个空格分开的正整数,代表了整个序列,从左向右依次编号为A1, A2……An。接下来Q行,每行两个整数i,j表示了一个询问区间。输入数据保证1≤i <j<=N

例题:

2038: [2009国家集训队]小Z的袜子(hose)

Description

作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿。终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命……
       具体来说,小Z把这N只袜子从1到N编号,然后从编号L到R(L 尽管小Z并不在意两只袜子是不是完整的一双,甚至不在意两只袜子是否一左一右,他却很在意袜子的颜色,毕竟穿两只不同色的袜子会很尴尬。
你的任务便是告诉小Z,他有多大的概率抽到两只颜色相同的袜子。当然,小Z希望这个概率尽量高,所以他可能会询问多个(L,R)以方便自己选择。

Input

输入文件第一行包含两个正整数N和M。N为袜子的数量,M为小Z所提的询问的数量。接下来一行包含N个正整数Ci,其中Ci表示第i只袜子的颜色,相同的颜色用相同的数字表示。再接下来M行,每行两个正整数L,R表示一个询问。

Output

包含M行,对于每个询问在一行中输出分数A/B表示从该询问的区间[L,R]中随机抽出两只袜子颜色相同的概率。若该概率为0则输出0/1,否则输出的A/B必须为最简分数。(详见样例)

【样例解释】

询问1:共C(5,2)=10种可能,其中抽出两个2有1种可能,抽出两个3有3种可能,概率为(1+3)/10=4/10=2/5。

询问2:共C(3,2)=3种可能,无法抽到颜色相同的袜子,概率为0/3=0/1。

询问3:共C(3,2)=3种可能,均为抽出两个3,概率为3/3=1/1。

注:上述C(a, b)表示组合数,组合数C(a, b)等价于在a个不同的物品中选取b个的选取方案数。

【数据规模和约定】

30%的数据中 N,M ≤ 5000;

60%的数据中 N,M ≤ 25000;

100%的数据中 N,M ≤ 50000,1 ≤ L < R ≤ N,Ci ≤ N。

单个测试点时限2S

对于上述这道题,30%的数据我们可以对于每个询问都扫描询问区间中所存在的数然后计算,这样单次复杂度是O(N)的,但有M的询问,总复杂度是O(MN)。这就显得有点不太能接受了。

但是当我们知道一个询问[l,r]的答案后,[l+1,r],[l-1,r],[l,r+1],[l,r-1]这四个区间的答案可以通过计算做到O(1)的时间内得到

所以我们可以考虑莫队算法,分为如下三步。

1、分块

2、把所有询问左端点排序

3、对于左端点在同一块内的询问按右端点排序,然后分三种情况统计。

而复杂度正如理论部分所说的一样,

一、i与i+1在同一块内,r单调递增,所以r是O(N)的。由于有sqrt(N)块,所以这一部分时间复杂度是Nsqrt(N)。
二、i与i+1跨越一块,r最多变化n,由于有sqrt(N)块,所以这一部分时间复杂度是Nsqrt(N)
三、i与i+1在同一块内时变化不超过sqrt(N),跨越一块也不会超过2* sqrt(N),不妨看作是sqrt(N)。由于有N个数,所以时间复杂度是O(Nsqrt(N))
可以证明复杂度是O(Nsqrt(N))了

理论2:

可现在有很多问题都设置了修改操作,对于这类我们我们又该如何处理呢?

问题:

我们现在有一个长为n的,对于序列,我们有m次操作,操作分为两种

1、询问在[l,r]中抽到两个数字相同的概率

2、把某个位置的数ai改成x

100%的数据中 N,M ≤100000,1 ≤ L < R ≤ N,Ci ≤ N。

单个测试点时限10S

我们会发现,加上了修改操作后。就没办法直接按照分块来处理解决询问的顺序。

定义B为分块的大小。

首先考虑没有修改操作,那么就和理论1中小Z的袜子一样,令B = sqrt(n) 。把所有询问左端点排序,对于左端点在同一块内的询问按右端点排序,然后写莫队算法,按顺序扫询问,这样是O(n sqrt(n))。如果现在加上修改操作考虑一个询问(l,r),这样是肯定不够的。

于是变成:(l,r,ti),ti是询问时的时间,即这次询问是第几次操作。把所有询问左端点l排序,对于左端点在同一块内的询问按右端点r所在的块排序,对右端点r所在块相同的我们再按照时间ti排序。

然后做莫队算法,按顺序扫询问,时间有时向前有时倒流。这样令B = n ^ (2 / 3),因为在每一块中时间最多从1到T改变一次,设询问操作p1次,修改操作p2次,则在最差情况下的时间复杂度是O(p1 n^(2 / 3)+p2 *n^(1 / 3)* n^(1 / 3))=O(n^(5 / 3))【n与m等价】,这在时限下基本是可以得到答案的。

那么还有个遗留的问题,如何处理时间。我们只需要记录修改前和修改后该点的值就可以了。

至此这个问题完美解决。

C语言实现:

#include <bits/stdc++.h>
#define PB push_back
#define MP make_pair
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
#define PI acos((double)-1)
#define E exp(double(1))
#define K 100000+9
int a[K],vis[K],pos[K*];
int sz,n,m;
double ans[K*],sum,num;
map<int,int>p;
struct node
{
int l,r,id;
}q[K*];
int cmp(node ta,node tb)
{
if(pos[ta.l]==pos[tb.l])return ta.r<tb.r;
return pos[ta.l]<pos[tb.l];
}
void add(int x)
{
if(!vis[p[a[x]]])sum+=a[x],num++;
vis[p[a[x]]]++;
}
void del(int x)
{
if(vis[p[a[x]]]==)sum-=a[x],num--;
vis[p[a[x]]]--;
}
void slove(void)
{
int l,r;
l=;r=;
sum=num=;
memset(vis,,sizeof(vis));
for(int i=;i<=m;i++)
{
while(l<q[i].l)del(l++);
while(l>q[i].l)add(--l);
while(r<q[i].r)add(++r);
while(r>q[i].r)del(r--);
ans[q[i].id]=sum*1.0/num;
}
} int main(void)
{
int t,cs=;
cin>>t;
while(t--)
{
scanf("%d",&n);
p.clear();
for(int i=,cnt=,x;i<=n;i++)
{
scanf("%d",&x);
if(p[x]==)p[x]=cnt++;
a[i]=x;
}
scanf("%d",&m);
sz=sqrt(n);
for(int i=;i<=m;i++)
{
scanf("%d%d",&q[i].l,&q[i].r);
pos[q[i].l]=(q[i].l-)/sz+;
q[i].id=i;
}
sort(q+,q+m+,cmp);
slove();
printf("Case %d:\n",cs++);
for(int i=;i<=m;i++)
printf("%.6f\n",ans[i]);
} return ;
}

总结:

也许莫队是一种看起来复杂度非常高的算法,但如果合理地处理好分块的大小和询问的顺序,,它便可以变成一个极其有效的工具。

辞谢

Vfleaking、莫涛

参考文献

国家集训队命题《小z的袜子》

莫队算法详解和c实现的更多相关文章

  1. BZOJ 3236 AHOI 2013 作业 莫队算法

    题目大意:给出一些数,问在一个区间中不同的数值有多少种,和在一个区间中不同的数值有多少个. 思路:因为没有改动,所以就想到了莫队算法.然后我写了5K+的曼哈顿距离最小生成树,然后果断T了.(100s的 ...

  2. B 洛谷 P3604 美好的每一天 [莫队算法]

    题目背景 时间限制3s,空间限制162MB 素晴らしき日々 我们的情人,不过是随便借个名字,用幻想吹出来的肥皂泡,把信拿去吧,你可以使假戏成真.我本来是无病呻吟,漫无目的的吐露爱情---现在这些漂泊不 ...

  3. BZOJ 1878 SDOI2009 HH的项链 树状数组/莫队算法

    题目大意:给定一个序列.求一个区间内有多少个不同的数 正解是树状数组 将全部区间依照左端点排序 然后每次仅仅统计左端点開始的每种颜色的第一个数即可了 用树状数组维护 我写的是莫队算法 莫队明显能搞 m ...

  4. NBUT 1457 莫队算法 离散化

    Sona Time Limit:5000MS     Memory Limit:65535KB     64bit IO Format: Submit Status Practice NBUT 145 ...

  5. BZOJ 2038: [2009国家集训队]小Z的袜子(hose) [莫队算法]【学习笔记】

    2038: [2009国家集训队]小Z的袜子(hose) Time Limit: 20 Sec  Memory Limit: 259 MBSubmit: 7687  Solved: 3516[Subm ...

  6. NPY and girls-HDU5145莫队算法

    Time Limit: 8000/4000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Problem Description ...

  7. Codeforces617 E . XOR and Favorite Number(莫队算法)

    XOR and Favorite Number time limit per test: 4 seconds memory limit per test: 256 megabytes input: s ...

  8. Bzoj 2038---[2009国家集训队]小Z的袜子(hose) 莫队算法

    题目链接 http://www.lydsy.com/JudgeOnline/problem.php?id=2038 Description 作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色 ...

  9. 【BZOJ-3052】糖果公园 树上带修莫队算法

    3052: [wc2013]糖果公园 Time Limit: 200 Sec  Memory Limit: 512 MBSubmit: 883  Solved: 419[Submit][Status] ...

随机推荐

  1. 在IIS服务上发布网站

    一.打开控制面板中的“管理工具” 二.打开IIS管理器 三.右键网站,选择“新建网站”

  2. CentOS 6.5系统安装编译安装MySQL 5.6详细过程

    点评:CentOS 6.5下通过yum安装的MySQL是5.1版的,比较老,那我们就通过源代码安装高版本的MySQL5.6.14.一:卸载旧版本使用下面的命令检查是否安装有MySQL Server r ...

  3. zipkin:和springcloud集成过程记录

    发现全是springcloudapp的名称,然后是springcloudapp(http://localhost:8080/hello/tom)工程单独调用并没有通知zipkin: 原来是因为rest ...

  4. Regenerate Script 重置脚本

    1.Regenerate Script 重置回录制后的第一次脚本,当修改设定后点击这个按钮,新的设置也会录制到 如:开始没有录到下载的文件,添加下载文件的个时候,再次点击重置,就录制到了 如:如开始是 ...

  5. JSP的taglib示例

    web.xml <?xml version="1.0" encoding="UTF-8"?> <web-app version="2 ...

  6. servlet的登陆案例

    Users.java package com.po; public class Users { private String username; private String password; pu ...

  7. Maven的依赖机制介绍

    以下内容引用自https://ayayui.gitbooks.io/tutorialspoint-maven/content/book/maven_manage_dependencies.html: ...

  8. 读Understanding the Linux Kernel, 3rd Edition有感

    14.3.2.2. Avoiding request queue congestion Each request queue has a maximum number of allowed pendi ...

  9. 浅谈Storm流式处理框架

    Hadoop的高吞吐,海量数据处理的能力使得人们可以方便地处理海量数据.但是,Hadoop的缺点也和它的优点同样鲜明——延迟大,响应缓慢,运维复杂. 有需求也就有创造,在Hadoop基本奠定了大数据霸 ...

  10. mybatis 2 -常用数据操作

    1.写入数据并获取自增ID XML配置: <!-- 写入数据获取自增ID --> <insert id="insertLog" parameterType=&qu ...