机房的众神犇都在搞这个东西,本SB
也掺和一下下吧。php
莫队算法可用于解决一类可离线且在获得区间\([l,r]\)的答案后,能在\(O(1)\)或\(O(\log_2{n})\)获得区间\([l,r+1]\)或\([l-1,r]\)的答案的问题算法
先看这样一个问题:数组
给出n个数字,m次询问,每次询问在区间\([l_i,r_i]\)之间任选两个数字相等的几率是多少。(n,q<=50000)(小z的袜子)数据结构
在区间\([l,r]\)中,这个几率是:
\[\frac{\sum_{i=1}^{v}C(2,f(i))}{C(2,r-l+1)}\] (v表示数字值,f(i)表示数字i在区间内出现的次数)post
因为没有加和性质,传统的线段树什么的彻底派不上用场了呢!优化
考虑分子,由于\(C(2,x)=\frac{x^2-x}{2}\),因此分子=\(\frac{\sum_{i=1}^{v}f(i)^2-\sum_{i=1}^{v}f(i)}{2}\)
显然 \(\sum_{i=1}^{v}f(i)=r-l+1\)spa
若得知区间\([l,r]\)的答案怎么求区间\([l,r+1]\)的答案呢?仔细想一想。恩,有了。区间\([l,r+1]\)与区间\([l,r]\)相比只多了一个元素Z,这种改动是很小的,那么前式中分子的值\(S=S_0-f(Z)^2+(f(Z)+1)^2-1=S_0+2*f(Z)\),同时++f(z),恩,\(O(1)\)的。这样的话,在处理下一个询问\([l_i,r_i]\)时,复杂度就是\(O(|r-r_i|+|l-l_i|)\)的。一样的方法,也能够在\(O(1)\)内求出\([l-1,r]\),\([l+1,r]\),\([l,r-1]\)。这样的方法对于随机数据表现是很好的,但也不难给出故意卡你的数据。.net
这时,就须要莫队算法来撑腰了,这也是莫队算法优化的精髓。code
注意到,每一个区间能够抽象成平面中的点,每次转移的花费都至关与从某点到另外一点的曼哈顿距离的长度。恩,因此呢?blog
因此咱们花费的即是这些平面中的点联通的曼哈顿距离。平面点的曼哈顿最小生成树!
对!但平面点的曼哈顿最小生成树怎么求呢?枚举两两点链接\(O(n^2)\),毫无心义。其实平面点的曼哈顿最小生成树有基于平面区域划分的\(O(nlog_2n)\)的求法,但咱们有更简洁的方法。对,分块!
神犇曰:分块是个好东西
确实,利用分块,咱们能够实现\(O(n\sqrt{n})\)的时间复杂度。虽然求解平面点的曼哈顿最小生成树是\(O(nlog_2n)\)的,但根据莫队论文中的证实,用到这里时,仍然是\(O(n\sqrt{n})\),只不过常数小一些罢了。
分块的作法:
取\(x=\sqrt(n)\),以\([1,x],[x+1,2x],[2x+1,3x]...\)分块
用pos数组维护端点i在第pos[i]块中,而后就搞呗。
这样搞:
1):排序,以左段点所在的块为第一关键字,以右端点为第二关键字
2):从左往右处理询问(离线)
3):不断调整l,r的位置并同时修改
时间复杂度证实:
右端点移动:
首先咱们考虑一个块里面的转移状况
因为一个块里面的询问都按右端点排序
因此咱们右端点在一个块里面最多移动n次
有 \(O(\sqrt{n})\)个块,那么同一个块内的右端点移动最多就是\(O(n\sqrt{n})\)
而后考虑从一个块到另外一个块致使的右端点变化
最坏状况,右端点由n到1,那么移动n次
有 \(O(\sqrt{n})\)个块
那么从一个块到另外一个块的事件只会发生\(O(\sqrt{n})\)次……
因此这种右端点移动的次数也是\(O(n\sqrt{n})\)次
没有别的事件致使右端点移动了
左端点移动:
同一个块里面,因为左端点都在一个长度为\(O(\sqrt{n})\)的区间里面
因此在同一块里面移动一次,左端点最多变化\(O(\sqrt{n})\)
总共有n个询问……
因此同一块里面的移动最多n次
那么同一个块里面的左端点变化最可能是\(O(n\sqrt{n})\)的
考虑跨越块
每由第i个块到第i+1个块,左端点最坏加上\(O(\sqrt{n})\)
总共能加上\(O(\sqrt{n})\)次
因此跨越块致使的左端点移动是\(O(n)\)的
综上,分块作法是\(O(n*\sqrt{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/