标签:复杂度 序列 不容易 数据结构 区间 代码 队列 处理 之间
莫队算法是莫涛队长发明的,为表示对他的尊敬,故称这种算法叫莫队。
一种处理序列操作的离线算法,适用范围广,复杂度一般带根号。
假设题目不涉及修改操作。
将所有操作离线,将所有操作进行二元组排序,第一维是左端点所在块的编号,第二维是右端点。
排序后,按照顺序处理询问,维护一个双端队列,同时维护队列内区间的答案,每次从$[L,R]$的答案,扩展到$[L-1,R]$ $[L,R-1]$ $[L+1,R]$ $[L,R+1]$的答案,最终得到当此询问区间的答案。
假设序列长度为$n$,询问数为$m$,分块块长为$d$,那么左端点在同一块内时,左端点移动每次不超过$d$,右端点总共移动不超过$n$,显然是$\Theta (m\times d+\frac{n^2}{d})$,令$d=\sqrt{n}$,$n,m$同阶,则时间复杂度为$\Theta(n\sqrt{n})$,常熟很大。
莫队算法的精髓在于,离线的情况下,通过调整询问与修改间的顺序,由已知的答案扩展到未知的答案,以优秀的计算顺序换取优秀的时间复杂度。
每个询问$[L,R]$可以看作平面上的整点$(L,R)$,和另一个询问$(l,r)$之间的曼哈顿距离即为两个询问间转移的代价。
我们要做的就是通过调整顺序,最小化询问间转移的代价之和。
最优的计算顺序即为这些点的曼哈顿最小生成树,然而求解这东西非常麻烦,于是我们采取直接排序这一更为暴力的方法,获得时间复杂度和代码复杂度的平衡。
当题目要求的操作较为复杂,限制性强,用线段树等数据结构不容易维护信息,或者维护信息复杂度较高,且题目对于时间的要求较为宽松时,支持离线,可以考虑使用莫队。
时间复杂度一般带根号,带修改莫队复杂度更高,且常数大,如果可以直接线段树或者分块干掉的题目,尽量不要使用莫队,有些题跑的真的很慢(即使你算出的时间复杂度非常优秀)。
适用条件:
在序列上由$[L,R]$的答案伸缩左右端点时,仅为$\Theta (1)$的复杂度。某些$\Theta (\log n)$转移答案的强行用莫队做,甚至不如$\Theta O(n^2)$暴力跑得快。
1.++和--是在前还是在后。
2.一般情况下四种操作的顺序无所谓,但是有的题是有影响的,比方说:BZOJ4358:permu。
普通莫队:
struct rec
{
int id;
int l;
int r;
int pos;
}q[N];
int a[N];
int ans[N];
bool cmp(rec a,rec b){return a.pos==b.pos?a.r<b.r:a.pos<b.pos;}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
int t=sqrt(n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id=i;
q[i].pos=(q[i].l-1)/t+1;
}
sort(q+1,q+m+1,cmp);
int l=1,r=0;
for(int i=1;i<=m;i++)
{
while(l>q[i].l)upd(--l,1);
while(r<q[i].r)upd(++r,1);
while(l<q[i].l)upd(l++,0);
while(r>q[i].r)upd(r--,0);
}
for(int i=1;i<=m;i++)
printf("%d\n",ans[i]);
return 0;
}
奇偶莫队:
bool cmp(rec a,rec b){return (a.pos)^(b.pos)?a.l<b.l:(((a.pos)&1)?a.r<b.r:a.r>b.r);}
rp++
标签:复杂度 序列 不容易 数据结构 区间 代码 队列 处理 之间
原文地址:https://www.cnblogs.com/wzc521/p/11240671.html