目录

【莫队算法】

【普通莫队】

【代码】

【题面】

【带修莫队】

【代码】

【题面】

【总结】


【莫队算法】

参考大米饼的莫队算法 ,目前的题型概括为三种:普通莫队,带修莫队以及树形莫队。

【普通莫队】

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

题意:给定编号1-n的n只袜子的颜色,输出从询问的区间[L,R]中随机抽出两只袜子颜色相同的概率。

首先考虑对于一个长度为n区间内的答案如何求解。

题目要求Ans使用最简分数表示:那么分母就是n*(n-1)->表示两两袜子之间的随机组合,分子是每种颜色可能配对情况的累加和,即该区间内每种颜色i出现次数sum[i]*(sum[i]-1)。

莫队算法的思路是,离线情况下对所有的询问进行一个美妙的SORT(),然后两个指针l,r(本题是两个,其他的题可能会更多)不断以看似暴力的方式在区间内跳来跳去,最终输出答案。

掌握一个思想基础:两个询问之间的状态跳转。

当前完成的询问的区间为[a,b],下一个询问的区间为[p,q],现在保存[a,b]区间内的每个颜色出现次数的sum[]数组已经准备好,[a,b]区间询问的答案Ans1已经准备好,怎样用这些条件求出[p,q]区间询问的Ans2?

考虑指针向左或向右移动一个单位,我们要付出多大的代价才能维护sum[]和Ans(即使得sum[],Ans保存的是当前[l,r]的正确信息)。

我们对图中a,b的向右移动一格进行分析:

a指针向右移动一个单位,所造成的后果就是:我们损失了一个i颜色的方块,那么我们要怎样维护呢?sum[i]-1即可。那Ans如何维护呢?分母从n*(n-1)变成(n-1)*(n-2),而分子中的其他颜色对应的部分是不会变的,故我们只需要修改答案中颜色为i的可能情况即可,即Ans-以前该颜色的答案贡献+现在的答案贡献。同理,b指针移动也是差不多的。

回归正题,我们发现知道一个区间的信息,要求出旁边区间的信息(旁边区间指的是当前区间的一个指针通过加一减一得到的区间),竟只需要O(1)的时间。

就算是这样,到这里为止的话莫队算法依旧无法焕发其光彩,原因是:如果我们以读入的顺序来枚举每个询问,每个询问到下一个询问时都用上述方法维护信息,那么在你脑海中会浮现出l,r跳来跳去的疯狂景象,疯狂之处在于最坏情况下时间复杂度为:O(n2) — 如果要这样还不如写一个暴力程序。

“莫队算法巧妙地将询问离线排序,使得其复杂度无比美妙……”。在一般做题时我们时常遇到使用排序来优化枚举时间消耗的例子。莫队的优化基于分块思想:对于两个询问,若在其l在同块,那么将其r作为排序关键字,若l不在同块,就将l作为关键字排序。这里使用Be[i]数组表示i所属的块是谁。

bool cmp(Mo a,Mo b){return Be[a.l]==Be[b.l]?a.r<b.r:a.l<b.l;}

值得强调的是,我们是在对询问进行操作。

时间复杂度分析(分类讨论思想):

首先,枚举m个答案,就一个m了。设分块大小为unit。

分类讨论:

①l的移动:若下一个询问与当前询问的l所在的块不同,那么只需要经过最多2*unit步可以使得l成功到达目标,复杂度为:O(m*unit)

②r的移动:r只有在Be[l]相同时才会有序(其余时候还是疯狂地乱跳,你知道,一提到乱跳,那么每一次最坏就要跳n次!),Be[l]什么时候相同?在同一块里面l就Be[]相同。对于每一个块,排序执行了第二关键字:r。所以这里面的r是单调递增的,所以枚举完一个块,r最多移动n次。总共有n/unit个块:复杂度为:O(n*n/unit)

总结:O(n*unit+n*n/unit)(n,m同级,就统一使用n)

根据基本不等式得:当unit为sqrt(n)时,得到莫队算法的真正复杂度:O(n*sqrt(n))

【代码】

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=50000+10;
int col[maxn],Be[maxn];
ll sum[maxn],ans;
struct Mo{int l,r,ID;ll A,B;
}f[maxn];
ll GCD(ll a,ll b){return b==0?a:GCD(b,a%b);}
bool cmp(Mo a,Mo b){return Be[a.l]==Be[b.l]?a.r<b.r:a.l<b.l;}
bool CMP(Mo a,Mo b){return a.ID<b.ID;}
void revise(int x,int add)
{ans-=sum[col[x]]*(sum[col[x]]-1);sum[col[x]]+=add;ans+=sum[col[x]]*(sum[col[x]]-1);
}
int main()
{int n,m; scanf("%d%d",&n,&m);int unit=sqrt(n);for(int i=1;i<=n;i++)scanf("%d",&col[i]),Be[i]=i/unit+1;for(int i=1;i<=m;i++)scanf("%d%d",&f[i].l,&f[i].r),f[i].ID=i;sort(f+1,f+m+1,cmp);int l=1,r=0;for(int i=1;i<=m;i++){while(l<f[i].l) revise(l,-1),l++;while(l>f[i].l) revise(l-1,1),l--;while(r<f[i].r) revise(r+1,1),r++;while(r>f[i].r) revise(r,-1),r--;if(f[i].l==f[i].r){f[i].A=0;f[i].B=1;continue;}f[i].A=ans;f[i].B=1LL*(f[i].r-f[i].l+1)*(f[i].r-f[i].l); //分母:len*(len-1)ll gcd=GCD(f[i].A,f[i].B);f[i].A/=gcd;f[i].B/=gcd;}sort(f+1,f+m+1,CMP);for(int i=1;i<=m;i++)printf("%lld/%lld\n",f[i].A,f[i].B);return 0;
}

【题面】

2038: 小Z的袜子

Time Limit: 20 Sec  Memory Limit: 259 MB
Submit: 15963  Solved: 7244
[Submit][Status][Discuss]

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必须为最简分数。(详见样例)

Sample Input

6 4
1 2 3 3 3 2
2 6
1 3
3 5
1 6

Sample Output

2/5
0/1
1/1
4/15

【样例解释】
询问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。

【带修莫队】

题目:2120: 数颜色

莫队可以修改?那不是爆炸了吗。这类爆炸的问题被称为带修莫队(可持久化莫队)。

按照美妙类比思想,可以引入一个“修改时间”,表示当前询问是发生在前Time个修改操作后的。也就是说,在进行莫队算法时,看看当前的询问和时间指针是否相符,然后进行时光倒流或者时光推移操作来保证答案正确性。

Sort的构造:仅靠原来的sort关键字会使得枚举每个询问都可能因为时间指针移动的缘故要移动n次,总共就n^2次,那还不如写暴力。为了防止这样的事情发生,再加入第三关键字Tim:

bool cmp(Query a,Query b)
{return Be[a.l]==Be[b.l]?(Be[a.r]==Be[b.r]?a.Tim<b.Tim:a.r<b.r):a.l<b.l;
}

如何理解时间复杂度?

首先,R和Tim的关系就像L和R的关系一样:只有在前者处于同块时,后者才会得到排序的恩赐,否则sort会去满足前者,使得后者开始乱跳。

依旧像上文那样:枚举m个答案,就一个m了。设分块大小为unit。

分类讨论:

①对于l指针,依旧是O(unit*n)

②对于r指针,依旧是O(n*n/unit)

③对于T指针(即Time):

类比r时间复杂度的计算。我们要寻找有多少个单调段(一个单调段下来最多移动n次)。上文提到,当且仅当两个询问l在同块,r也在同块时,才会对可怜的Tim进行排序。那么对于每一个l的块,里面r最坏情况下占据了所有的块,所以最坏情况下:有n/unit个l的块,每个l的块中会有n/unit个r的块,此时,在一个r块里,就会出现有序的Tim。所以Tim的单调段个数为:(n/unit)*(n/unit)。每个单调段最多移动n次。

所以:O((n/unit)2*n)

三个指针汇总:O(unit*n+n2/unit+(n/unit)2*n)

【代码】

#include <bits/stdc++.h>
using namespace std;
const int maxn=10000+10;
struct Query{int l,r,Tim,ID;
}q[maxn];
struct Change{int pos,New,Old;
}c[maxn];
int s[maxn],Be[maxn];
int now[maxn],ans[maxn];
int color[maxn*100];
int Ans,l=1,r;
bool cmp(Query a,Query b)
{return Be[a.l]==Be[b.l]?(Be[a.r]==Be[b.r]?a.Tim<b.Tim:a.r<b.r):a.l<b.l;
}
void revise(int x,int d)
{color[x]+=d;if(d>0) Ans+=color[x]==1;if(d<0) Ans-=color[x]==0;
}
void going(int x,int d)
{if(l<=x&&x<=r) revise(d,1),revise(s[x],-1);s[x]=d;
}
int main()
{int n,m; scanf("%d%d",&n,&m);int unit=pow(n,2.0/3); for(int i=1;i<=n;i++){scanf("%d",&s[i]);now[i]=s[i];Be[i]=i/unit+1; //分块}int Time=0,t=0;while(m--){char sign; int x,y;scanf(" %c %d%d",&sign,&x,&y);if(sign=='Q') q[++t]=(Query){x,y,Time,t};else c[++Time]=(Change){x,y,now[x]},now[x]=y;}sort(q+1,q+t+1,cmp);int T=0;for(int i=1;i<=t;i++){while(T<q[i].Tim) going(c[T+1].pos,c[T+1].New),T++;while(T>q[i].Tim) going(c[T].pos,c[T].Old),T--;while(l<q[i].l) revise(s[l],-1),l++;while(l>q[i].l) revise(s[l-1],1),l--;while(r<q[i].r) revise(s[r+1],1),r++;while(r>q[i].r) revise(s[r],-1),r--;ans[q[i].ID]=Ans;}for(int i=1;i<=t;i++) printf("%d\n",ans[i]);return 0;
}

【题面】

2120: 数颜色

Time Limit: 6 Sec  Memory Limit: 259 MB
Submit: 10682  Solved: 4451
[Submit][Status][Discuss]

Description

墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。墨墨会像你发布如下指令: 1、 Q L R代表询问你从第L支画笔到第R支画笔中共有几种不同颜色的画笔。 2、 R P Col 把第P支画笔替换为颜色Col。为了满足墨墨的要求,你知道你需要干什么了吗?

Input

第1行两个整数N,M,分别代表初始画笔的数量以及墨墨会做的事情的个数。第2行N个整数,分别代表初始画笔排中第i支画笔的颜色。第3行到第2+M行,每行分别代表墨墨会做的一件事情,格式见题干部分。

Output

对于每一个Query的询问,你需要在对应的行中给出一个数字,代表第L支画笔到第R支画笔中共有几种不同颜色的画笔。

Sample Input

6 5
1 2 3 4 5 5
Q 1 4
Q 2 6
R 1 2
Q 1 4
Q 2 6

Sample Output

4
4
3
4

HINT

对于100%的数据,N≤10000,M≤10000,修改操作不多于1000次,所有的输入数据中出现的所有整数均大于等于1且不超过10^6。

【总结】

莫队算法适用条件是比较苛刻的吗?是的。

①题目必须离线。

②能够以极少的时间推出旁边区间(一般是O(1))。

③没有修改或者修改不太苛刻。

④基于分块,分块不行,它也好不了哪里去(何况现在还有可持久化数据结构维护的分块)。

但莫队的思想美妙,代码优美,你值得拥有。莫队的排序思想也为众多离线处理的题目提供了完整的思路。

普通、带修(可持久化)莫队算法入门例题详解相关推荐

  1. 小Z的袜子(hose) (莫队算法入门)

    小Z的袜子(hose) 作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿.终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命-- 具体来说,小Z把这N只 ...

  2. 三大算法之三:贪心算法及其例题详解

    目录 零.前言 1.区分贪心算法和动态规划 1.动态规划 2.贪心算法 3.共通点 2.贪心算法得到最优解的条件 1.具有优化子结构 2.具有贪心选择性 3.任务安排问题 1.问题定义 2.优化子结构 ...

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

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

  4. 莫队算法 (普通莫队、带修莫队、树上莫队)

    莫队算法 主要基于分块的思想 用结构体记录询问的左右端点及询问编号 (这是一个离线算法) 通过排序优化指针扫描顺序优化时间复杂度 . 1.普通莫队 例题:SP3267 DQUERY - D-query ...

  5. 莫队算法学习笔记(二)——带修莫队

    前言:什么是莫队 莫队算法,是一个十分优雅的暴力. 普通的莫队可以轻松解决一些离线问题,但是,当遇上了一些有修改操作的问题,普通莫队就无能为力了. 于是,改进后的莫队--带修莫队就这样产生了. L i ...

  6. 【BZOJ】3052: [wc2013]糖果公园 树分块+带修改莫队算法

    [题目]#58. [WC2013]糖果公园 [题意]给定n个点的树,m种糖果,每个点有糖果ci.给定n个数wi和m个数vi,第i颗糖果第j次品尝的价值是v(i)*w(j).q次询问一条链上每个点价值的 ...

  7. 曼哈顿距离最小生成树莫队算法

    参考资料:https://www.cnblogs.com/CsOH/p/5904430.html https://blog.csdn.net/huzecong/article/details/8576 ...

  8. 莫队入门例题之持久化莫队:2120: 数颜色

    ·述大意:        多个区间询问,询问[l,r]中颜色的种类数.可以单点修改颜色. ·分析: 莫队可以修改?那不是爆炸了吗. 这类爆炸的问题被称为带修莫队(可持久化莫队). 按照美妙类比思想,可 ...

  9. 分治 —— 莫队算法

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

最新文章

  1. exp导oracle数据库,使用exp/imp 在oracle数据库间导数据
  2. MySQL远程表访问设置
  3. 每日一皮:你们都是怎么解压的?
  4. tl r402路由器设置_家里新安装宽带如何连接路由器 家里新安装宽带连接路由器方法【详解】...
  5. linux系统远程工具,分享|Remmina:一个 Linux 下功能丰富的远程桌面共享工具
  6. 迈向现代化的 .Net 配置指北
  7. 中国石油计算机文化基础答案,中国石油大学17年秋《计算机文化基础》第二次在线作业答案...
  8. Python基础语法学习整理
  9. PAC自动代理文件格式,教你如何写PAC文件
  10. 【Linux远程管理】RDP协议远程管理
  11. Salesforce和SAP HANA的元数据访问加速
  12. 《Using OpenRefine》翻译~15
  13. 官宣:华为云学院带你看AI
  14. 罗老师算法竞赛专题解析
  15. 公司寄件管理平台必要性分析
  16. 岁月温柔-18 妈妈在市ICU第8天
  17. 数据库关系代数思维导图
  18. access是用来干什么的_Access数据库是做什么的?
  19. 文艺中年高晓松成“岛主” 上万册图书免费看
  20. redhat7.6添加中文语言支持

热门文章

  1. 拓嘉启远:遇到恶劣的拼多多买家怎么办
  2. JSON对象转为Java类对象
  3. 机器学习实战之决策树熵的概述
  4. Canal(1):Canal入门
  5. vue中 slot 的使用总结
  6. windows下异步IO一
  7. IDEA Maven SpringMVC中使用thyme leaf
  8. 爬虫进阶:反反爬虫技巧!
  9. 基于python的unittest单元测试C语言代码
  10. git命令进行代码回滚