【思想基础】

普通莫队常用于维护区间答案,比如:对于一个长度为 n 的序列,给出 m 次询问,每次询问区间 [l,r] 内有多少个不同的颜色,其中 n,m<=100000.

首先考虑暴力,对于每次询问,遍历一遍 [l,r],这样的时间复杂度是 O(n*m) 的,最坏时间复杂度肯定会超时,那么考虑换一种方式进行暴力。

定义 ql、qr,表示区间 [ql,qr] 内有多少种颜色,再定义 cnt 数组,cnt[i] 表示第 i 种颜色在区间 [ql,qr] 中出现的次数,然后一个个处理询问,对于询问 [l,r],挪动 ql 到 l,qr 到 r.

以下图为例,进行模拟:

对于区间 [ql,qr],初始状态如上,假设蓝色为 1,红色为 2,绿色为 3,那么:cnt_1=3,cnt_2=3,cnt_3=1

将 qr 向右挪一下,那么多了一个绿色,使得:cnt_3=cnt_3+1=2

将 qr 继续向右挪动,那么多了一个红色,使得:cnt_2=cnt_2+1=4,此时可以发现,右指针 qr 与询问右端点 r 重合,那么可以对左指针进行挪动

对 ql 向右挪动,那么少了一个蓝色,使得:cnt_1=cnt_1-1=2,此时左指针 ql 与询问左端点 l 重合,可得出答案:cnt_1=2,cnt_2=4,cnt_3=2.

通过以上模拟可以发现,每次挪动都是 O(1),每次询问最多挪动 n 次,这样时间复杂度依旧是 O(n*m),但通过对以上过程的模拟可以发现,这样暴力的耗时就消耗在挪动次数上,因此只要让挪动的次数尽可能的少就可以极大的降低时间复杂度。

而要想让挪动次数尽可能的小,可以将 m 次询问全部存储下来,然后按照某种方法进行排序,从而减少挪动次数,但这样的方法是强行离线,然后进行排序,因此普通莫队是不支持修改的。

int l=1,r=0,ans=0;
for(int i=1;i<=m;i++){while(l>q[i].l) add(--l);//[l-1,r]while(l<q[i].l) del(l++);//[l+1,r]while(r<q[i].r) add(++r);//[l,r+1]while(r>q[i].r) del(r--);//[l,r-1]res[q[i].id]=ans;//存储答案
}

【分块】

对于 n 与 m 同阶的情况,一般设块长度为 ,经过排序后,每个块内均摊有  个询问的 l 左端点,那么显然这 l 个端点的右端点是有序的,最多会移动 n 次,因此对于每个块的时间复杂度是 O(n),然后有  个块,那么这样的总复杂度为 ,而对于询问 m 特别大的情况, 会超时,因此需要用到其他的长度。

设块长度为 S,那么对于任意多个在同一块内的询问,挪动的距离就是 n,一共有  个块,移动的总次数就是 ,由于移动时可能会跨块,因此还需要加上一个 O(m*S) 的复杂度,故而总复杂度为 ,由于我们需要让这个值尽可能的小,通过简单的数学运算可以得出,S 取  是最优的,此时时间复杂度为 ,而在随机情况下,块的大小为  是最优的,大约是原来的 0.9 倍。

需要注意的是,分块时块的大小不是固定的,要根据题目具体分析,分析的过程如上面分析 m 极大时的复杂度。

block=n/sqrt(m*2/3*1.0);//分块,不卡常数时
block=sqrt(m*2/3*1.0);//分块,卡常数时

【排序】

将 m 次询问强制离线进行进行排序,一种方法是优先按照左端点进行排序,这样的排序可以保证左端点只会右挪,但右端点最坏的情况还是每次从最前面挪动到最后面,再从最后面挪到最前面,这样的时间复杂度依然是 O(n*m),因此要考虑一种使左右端点挪动次数尽可能少的排序方法。

考虑将长度为 n 的序列分为  个长度为  的块,若左端点在同一个块内,则按照右端点排序,即以左端点所在块为第一关键字,右端点位置为第二关键字。

bool cmp(node a,node b){//正常排序if(a.l/block==b.l/block)//左端点在一个块中return a.r<b.r;//按照右端点从小到大排序else//左端点不在一个块中return a.l/block<b.l/block;//按照块的位置进行排序
}

正常排序时,由于每个块的右端点都是按照从小到大排序的,当指针跳回左边后处理下一个块又要跳回右边,增加了不必要的移动,因此,此时可以按照奇偶性排序进行优化:当左端点的块为奇数时,右端点按照从小到大排;当左端点的块偶数时,右端点按照从大到小排。这样可以保证指针移到右边不用再跳回左边,减少一半的操作,理论上可以快一倍。

bool cmp(Node a,Node b){//按照奇偶性排序if( (a.l/block)==(b.l/block) ){//当左端点位于同一个块时if( (a.l/block)%2 )//左端点的块序号为奇数时return a.r<b.r;//按照从小到大排else//左端点的块序号为偶数时return a.r>b.r;//按照从大到小排}else//当左端点不位于同一个块时return a.l<b.l;//按照块的位置进行排序//return (a.l/block)^(b.l/block) ? a.l<b.l : ( ((a.l/block)&1)?a.r<b.r:a.r>b.r );
}

【模版】

1.统计 [l,r] 中相同的数的个数

struct Node{int l,r;//询问的左右端点int id;//询问的编号
}q[N];
int n,m,a[N];
int block;//分块
int ans,cnt[N];//cnt[i]为i在当前区间出现次数
int res[N];bool cmp(Node a,Node b){//奇偶性排序return (a.l/block)^(b.l/block)?a.l<b.l:(((a.l/block)&1)?a.r<b.r:a.r>b.r);
}
void add(int x){//统计新的if(!cnt[a[x]])ans++;cnt[a[x]]++;
}
void del(int x){//减去旧的cnt[a[x]]--;if(!cnt[a[x]])ans--;
}
int main(){//序列scanf("%d",&n);for(int i=1;i<=n;++i)scanf("%d",&a[i]);//询问scanf("%d",&m);for(int i=1;i<=m;i++){scanf("%d%d",&q[i].l,&q[i].r);q[i].id=i;}block=n/sqrt(m*2/3*1.0);//分块,不卡常数时//block=sqrt(m*2/3*1.0);//分块,卡常数时sort(q+1,q+m+1,cmp);//对询问进行排序int l=1,r=0;//左右指针for(int i=1;i<=m;i++){int ql=q[i].l,qr=q[i].r;//询问的左右端点while(l>ql) add(--l);//[l-1,r]while(l<ql) del(l++);//[l+1,r]while(r<qr) add(++r);//[l,r+1]while(r>qr) del(r--);//[l,r-1]res[q[i].id]=ans;//获取答案}for(int i=1;i<=m;i++)printf("%d\n",res[i]);return 0;
}

2. 统计 [l,r] 中有出现次数与其值相同的数的个数

struct Node{int l,r;//询问的左右端点int id;//询问的编号
}q[N];
int n,m,a[N];
int block;//分块
LL ans,cnt[N];//cnt[i]为i在当前区间出现次数
LL res[N];bool cmp(Node a,Node b){//奇偶性排序return (a.l/block)^(b.l/block)?a.l<b.l:(((a.l/block)&1)?a.r<b.r:a.r>b.r);
}void add(int x){//统计新的if(cnt[a[x]]==a[x])ans--;cnt[a[x]]++;if(cnt[a[x]]==a[x])ans++;
}
void del(int x){//减去旧的if(cnt[a[x]]==a[x])ans--;cnt[a[x]]--;if(cnt[a[x]]==a[x])ans++;
}
int main(){while(scanf("%d%d",&n,&m)!=EOF){for(int i=1;i<=n;++i){scanf("%d",&a[i]);if(a[i]>n)//大于n肯定不符合a[i]=-1;}for(int i=1;i<=m;i++){scanf("%d%d",&q[i].l,&q[i].r);q[i].id=i;}ans=0;memset(cnt,0,sizeof(cnt));block=sqrt(m*2/3*1.0);//分块,卡常数sort(q+1,q+m+1,cmp);//对询问进行排序int l=1,r=0;//左右指针for(int i=1;i<=m;i++){int ql=q[i].l,qr=q[i].r;//询问的左右端点while(l>ql) add(--l);//[l-1,r]while(l<ql) del(l++);//[l+1,r]while(r<qr) add(++r);//[l,r+1]while(r>qr) del(r--);//[l,r-1]res[q[i].id]=ans;//获取答案}for(int i=1;i<=m;i++)printf("%lld\n",res[i]);}return 0;
}

分治 —— 莫队算法 —— 普通莫队相关推荐

  1. 清橙A1206 小Z的袜子(莫队算法)

    A1206. 小Z的袜子 时间限制:1.0s   内存限制:512.0MB   总提交次数:744   AC次数:210   平均分:44.44 将本题分享到:        查看未格式化的试题    ...

  2. 莫队算法(小Z的袜子)

    目前的题型概括为三种:普通莫队,树形莫队以及带修莫队. 例题一:小Z的袜子 ·述大意: 进行区间询问[l,r],输出该区间内随机抽两次抽到相同颜色袜子的概率. ·分析: 对于L,R的询问.设其中颜色为 ...

  3. SPOJ DQUERY - D-query (莫队算法)

    Given a sequence of n numbers a1, a2, ..., an and a number of d-queries. A d-query is a pair (i, j) ...

  4. 【打CF,学算法——四星级】CodeForces 86D Powerful array (莫队算法)

    [CF简介] 题目链接:CF 86D 题面: D. Powerful array time limit per test 5 seconds memory limit per test 256 meg ...

  5. 分块算法:莫队(持续更新)

    分块,然后捡到莫队 文章目录 分块算法 普通莫队 前言 HDU多校连续两天都遇到了莫队的题,于是蒟蒻缓慢地开始学习莫队算法 初见莫队,只知道是个离线区间工具,以为会有点复杂.但其实基础的莫队很好理解, ...

  6. 离线区间的神奇——莫队算法

    离线区间的神奇--莫队算法 前言 一.什么是莫队算法? 二.例题分析: 1.相关例题 题目描述 输入格式 输出格式 简单分析: 三.莫队算法思想: 那么要怎么做(预处理):莫队算法优化的核心是**分块 ...

  7. 【莫队/树上莫队/回滚莫队】原理详解及例题:小B的询问(普通莫队),Count on a tree II(树上莫队),kangaroos(回滚莫队)

    文章目录 问题引入 介绍莫队算法及其实现过程 时间复杂度 莫队算法适用范围 莫队奇偶优化 普通莫队:小B的询问 树上莫队:SP10707 COT2 - Count on a tree II 回滚莫队: ...

  8. 分治 —— 莫队算法

    [概述] 莫队算法(mo's algorithm)是用来解决离线区间不修改询问问题,可以将复杂度优化到 O(n^1.5),除去普通的莫队算法外,还有带修改的莫队.树上莫队等等. 莫队常用于维护区间答案 ...

  9. NBUT 1457 Sona(莫队算法+离散化)

    [1457] Sona 时间限制: 5000 ms 内存限制: 65535 K 问题描述 Sona, Maven of the Strings. Of cause, she can play the ...

最新文章

  1. 浅析企业网站软文效果是如何展现的?
  2. java Reference
  3. CSDNmarkdown编辑器直接写代码的小效果(一生愿)
  4. C#学习小结(DAY1)
  5. JSP简单练习-省略显示长字符串
  6. WindowsFormsHost使用问题
  7. 学长的求职经验 记录【就业创业信息网、求职流程、求职小细节】
  8. 从当前元素继续寻找_云漫圈 | 寻找无序数组的第k大元素
  9. MarkDownPad2 注册码
  10. Linux实现的IEEE 802.q VLAN
  11. 用c语言编译二叉树,C语言 数据结构平衡二叉树实例详解
  12. 软件工程个人作业4(课堂练习课堂作业)
  13. poi导出excel设置对应格式
  14. 截图上传录屏gif上传工具推荐
  15. BIM技术在各阶段的应用简单介绍
  16. c语言汉字utf8,C语言汉字gbk转utf-8
  17. html把图片色调一致,ps怎样让两张不同的图片色调统一
  18. Linux学生QT成绩查询系统大作业报告,GitHub - cxc1357/Student-Information-Manage-System: QT实现的学生信息管理系统,高程大作业...
  19. html个人简历制作
  20. 一个Python的列表参数是如何搞垮一个网站的

热门文章

  1. 谁说IT男没有审美?怀揣5000元巨款,我们这样改变生活
  2. 180页PPT,讲解人工智能技术与产业发展
  3. FreeModbus输入寄存器
  4. c++ sleep函数_《PHP扩展开发》-hook-(hook原来的sleep)
  5. python编程多行输入_python多行输入的方法有哪些
  6. 热血上头!程序员想拍桌子离职的1000个瞬间...
  7. JAVA8之妙用Optional解决判断Null为空问题
  8. 我说我了解集合类,面试官竟然问我为啥HashMap的负载因子不设置成1!?
  9. 这才是程序员加班的真正原因!
  10. SAP JCo的Server/Client编程实例