莫队算法——_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;
}


posted @ 2020-12-13 11:03  zxsoul  阅读(134)  评论(1编辑  收藏  举报