莫队算法——_Thortzy
莫队算法——暴力出奇迹的由来
\(\texttt{graceful violence}\)
来源
要想了解莫队算法,就要先了解一下他的发明者
- 莫涛,由于是省队队长,因此算法为莫队
算法介绍
莫队,简单的来讲就是完成区间查询的任务,广义上来讲给人的感觉和线段树出不多,但线段树的限制条件是区间满足加和性质(即答案有小答案合并得到,例如加和),但是像区间颜色种类的非加和性质的,线段树就相对复杂(状压)
进入正题,莫队由三部分组成
- 区间查询按分块排序
- \(O(1)\) 完成四种操作(实际两种)
- 进行其他维护(据题意)
区间查询按分块排序
为什么要分块并且排序?因为莫队实现的原理就是利用分块进行时间和空间的优化,对于一个长度为 \(n\) 的数列 ,若将其平均分成 \(n^{\tfrac{1}{2}}\) 块,那么每块的长度为 \(n^{\tfrac{1}{2}}\),记为 \(s_\sqrt n\)
我们令查询的区间为 \(l\) 和 \(r\) ,共 \(m\) 次, 对于每个 \(l_i\) 都会位于某一个 \(s_i\) 的区间内,那么可以按照 \(l\) 所在的块区间进行排序,对于不同的 \(l\) 对应相同的 \(s\) 就按 \(r\) 排序
我们的目的是就是为后面 \(O(1)\) 完成四种操作做预处理(重点在 \(O(1)\) 上)
四种操作
我们令 \(f(l,r)\) 表示 区间为 \(l-r+1\) 的区间操作得到的 \(ans\) (可以是统计颜色个数,最值等),那么我们需要完成以下操作
f(l + 1, r);
f(l - 1, r);
f(x, r - 1);
f(x, r + 1);
都是 \(+1\) 或 \(-1\) 所以可以\(O(1)\) 实现
执行的目的就是使我们 \(l\) 和 \(r\) 可以 反复横跳 ,即可以灵活的在数列 \(n\) 上来回跳,从而快速的找到查询区间
比较快的原因
这里先点明莫队的时间复杂的为 \(O(m\times \sqrt n)(m次数,n序列长度)\)
例题
P1494 [国家集训队]小Z的袜子
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long ll;
const int manx=1e6+10;
const int mamx = 1e6 + 11;
const ll mod = 2123400401301379571;
const int inf = 0x3f3f3f3f;
inline int read() {
char c = getchar(); int x = 0, f = 1;
for ( ; !isdigit(c); c = getchar()) if (c == '-') f = -1;
for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
return x * f;
}
ll c(ll x){
if (x < 2) return 0;
return x * (x - 1) / 2;
}
int sqrt_pp, l, r, n, m, se[manx], cs[manx], ans, n_[manx], m_[manx];
struct node{
int l, r, id;
}query[manx];
inline int cmp (node a,node b){
if(a.l / sqrt_pp == b.l / sqrt_pp)
return a.r < b.r;
return a.l / sqrt_pp < b.l / sqrt_pp;
}
inline void del(int x){
int tp = --cs[se[x]];
ans -= c(tp + 1);
ans += c(tp);
}
inline void add(int x){
int tp = ++cs[se[x]];
ans -= c(tp - 1);
ans += c(tp);
}
int main(){
n = read(); m = read();
for (int i = 1; i <= n; i++) se[i] = read();
for (int i = 1; i <= m; i++){
query[i].l = read();
query[i].r = read();
query[i].id = i;
}
sqrt_pp = sqrt(n + 0.5);
sort(query+1,query+1+m,cmp);
l = 0,r = -1,ans = 0;
for (int i = 1; i <= m; i++){
while (l > query[i].l ) l--, add(l);
while (r < query[i].r ) r++, add(r);
while (l < query[i].l ) del(l), l++;
while (r > query[i].r ) del(r), r--;
n_[query[i].id] = ans;
m_[query[i].id] = c(r - l + 1);
}
for (int i = 1; i <= m; i++){
if (n_[i] == 0){
cout<< "0/1" <<endl;
continue;
}
ll tp = __gcd (m_[i], n_[i]);
cout<< n_[i]/tp <<"/"<< m_[i]/tp <<endl;
}
return 0;
}