莫队算法
机房的众神犇都在搞这个东西,本SB
也掺和一下下吧。
莫队算法可用于解决一类可离线且在得到区间\([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在区间内出现的次数)
由于没有加和性质,传统的线段树什么的完全派不上用场了呢!
考虑分子,因为$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$
若得知区间$[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]$。这样的方法对于随机数据表现是很好的,但也不难给出故意卡你的数据。
这时,就需要莫队算法来撑腰了,这也是莫队算法优化的精髓。
注意到,每个区间可以抽象成平面中的点,每次转移的花费都相当与从某点到另一点的曼哈顿距离的长度。恩,所以呢?
所以我们花费的便是这些平面中的点联通的曼哈顿距离。平面点的曼哈顿最小生成树!
对!但平面点的曼哈顿最小生成树怎么求呢?枚举两两点连接$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的袜子 分块 莫队算法
```cpp
#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://foreseeable97.logdown.com/posts/158522-233333)
[http://ydcydcy1.blog.163.com/blog/static/21608904020134411543898/](http://ydcydcy1.blog.163.com/blog/static/21608904020134411543898/)
[http://vawait.com/manhattanmst/](http://vawait.com/manhattanmst/)
[http://blog.csdn.net/huzecong/article/details/8576908](http://blog.csdn.net/huzecong/article/details/8576908)\]
人的一切痛苦,本质上都是对自己的无能的愤怒。