普通、带修(可持久化)莫队算法入门例题详解
目录
【莫队算法】
【普通莫队】
【代码】
【题面】
【带修莫队】
【代码】
【题面】
【总结】
【莫队算法】
参考大米饼的莫队算法 ,目前的题型概括为三种:普通莫队,带修莫队以及树形莫队。
【普通莫队】
例题: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))。
③没有修改或者修改不太苛刻。
④基于分块,分块不行,它也好不了哪里去(何况现在还有可持久化数据结构维护的分块)。
但莫队的思想美妙,代码优美,你值得拥有。莫队的排序思想也为众多离线处理的题目提供了完整的思路。
普通、带修(可持久化)莫队算法入门例题详解相关推荐
- 小Z的袜子(hose) (莫队算法入门)
小Z的袜子(hose) 作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿.终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命-- 具体来说,小Z把这N只 ...
- 三大算法之三:贪心算法及其例题详解
目录 零.前言 1.区分贪心算法和动态规划 1.动态规划 2.贪心算法 3.共通点 2.贪心算法得到最优解的条件 1.具有优化子结构 2.具有贪心选择性 3.任务安排问题 1.问题定义 2.优化子结构 ...
- NBUT 1457 Sona(莫队算法+离散化)
[1457] Sona 时间限制: 5000 ms 内存限制: 65535 K 问题描述 Sona, Maven of the Strings. Of cause, she can play the ...
- 莫队算法 (普通莫队、带修莫队、树上莫队)
莫队算法 主要基于分块的思想 用结构体记录询问的左右端点及询问编号 (这是一个离线算法) 通过排序优化指针扫描顺序优化时间复杂度 . 1.普通莫队 例题:SP3267 DQUERY - D-query ...
- 莫队算法学习笔记(二)——带修莫队
前言:什么是莫队 莫队算法,是一个十分优雅的暴力. 普通的莫队可以轻松解决一些离线问题,但是,当遇上了一些有修改操作的问题,普通莫队就无能为力了. 于是,改进后的莫队--带修莫队就这样产生了. L i ...
- 【BZOJ】3052: [wc2013]糖果公园 树分块+带修改莫队算法
[题目]#58. [WC2013]糖果公园 [题意]给定n个点的树,m种糖果,每个点有糖果ci.给定n个数wi和m个数vi,第i颗糖果第j次品尝的价值是v(i)*w(j).q次询问一条链上每个点价值的 ...
- 曼哈顿距离最小生成树莫队算法
参考资料:https://www.cnblogs.com/CsOH/p/5904430.html https://blog.csdn.net/huzecong/article/details/8576 ...
- 莫队入门例题之持久化莫队:2120: 数颜色
·述大意: 多个区间询问,询问[l,r]中颜色的种类数.可以单点修改颜色. ·分析: 莫队可以修改?那不是爆炸了吗. 这类爆炸的问题被称为带修莫队(可持久化莫队). 按照美妙类比思想,可 ...
- 分治 —— 莫队算法
[概述] 莫队算法(mo's algorithm)是用来解决离线区间不修改询问问题,可以将复杂度优化到 O(n^1.5),除去普通的莫队算法外,还有带修改的莫队.树上莫队等等. 莫队常用于维护区间答案 ...
最新文章
- exp导oracle数据库,使用exp/imp 在oracle数据库间导数据
- MySQL远程表访问设置
- 每日一皮:你们都是怎么解压的?
- tl r402路由器设置_家里新安装宽带如何连接路由器 家里新安装宽带连接路由器方法【详解】...
- linux系统远程工具,分享|Remmina:一个 Linux 下功能丰富的远程桌面共享工具
- 迈向现代化的 .Net 配置指北
- 中国石油计算机文化基础答案,中国石油大学17年秋《计算机文化基础》第二次在线作业答案...
- Python基础语法学习整理
- PAC自动代理文件格式,教你如何写PAC文件
- 【Linux远程管理】RDP协议远程管理
- Salesforce和SAP HANA的元数据访问加速
- 《Using OpenRefine》翻译~15
- 官宣:华为云学院带你看AI
- 罗老师算法竞赛专题解析
- 公司寄件管理平台必要性分析
- 岁月温柔-18 妈妈在市ICU第8天
- 数据库关系代数思维导图
- access是用来干什么的_Access数据库是做什么的?
- 文艺中年高晓松成“岛主” 上万册图书免费看
- redhat7.6添加中文语言支持