莫队算法(区间处理)
0x00 概论
莫队算法主要是用于离线解决 通常不带修改只有查询的一类区间问题。
以前遇到区间问题的时候一般都是用线段树解决,当然能用线段树解决的问题也在多数。线段树的主要思路就是通过一个左半拉序列 和一个右半拉序列 来维护它们的父亲(也就是两条序列接合在一起的完整序列),通过层层递归下去从而完成对整体序列的维护。但在有些时候可能会遇到如下的问题:
给定一个大小为N的数组,数组中所有元素的大小a[i]<=N。你需要回答M个查询。每个查询的形式是L,R。 你需要回答在范围[ L,R ]中至少重复3次的数字的个数。
假设此时我们已经分别维护好了左右两边序列中每个数字重复的个数,然后开始向上维护,我们就会发现。。。
。。。
我们依然要把每个数字在左右序列中出现的次数加起来才能完成对一个父节点的维护……换而言之,我们并不能在或者某个很短的时间内完成对线段树单一节点的维护(如果神犇能够无视这一句话请无视这一句话……)。
。。。
这时我们发现如果我们已知一个区间的情况,我们可以在的时间内确定的情况(只需开一个数组记录每一个数出现的次数,然后将的出现次数加一即可)。这样我们可以在的时间内完成对所有区间的扫描。
// 这里有一段N^2的伪代码
接着我们发现,我们不仅可以确定,还可以确定,和。这个时候,就可以用到莫队算法啦。
0x01 莫队算法
莫队的精髓就在于,离线得到了一堆需要处理的区间后,合理的安排这些区间计算的次序以得到一个较优的复杂度。
再次假设我们已知区间,需要计算的区间为,由于和分别只能单步转移,所以需要的时间复杂度为。相当于把两个区间分别看成是平面上的两个整点和,两点之间的转移开销为两点之间的曼哈顿距离。连接所有点的最优方案为一棵树,那么整体的时间复杂度就是这棵树上所有曼哈顿距离之和。
于是乎最优的复杂度肯定是这棵树是最小生成树的时候,也就是曼哈顿距离最小生成树。
但这么打貌似代码复杂度有点大。。而且在实际的转移中肯定会出现分支,需要建边(结构是一棵树),那么有没有什么赛艇的替代品可以少敲代码并在一重循环里完成暴力转移呢?
当然是有的,我们先对序列分块,然后以询问左端点所在的分块的序号为第一关键字,右端点的大小为第二关键字进行排序,按照排序好的顺序计算,复杂度就会大大降低。
- 分块相同时,右端点递增是的,分块共有个,复杂度为
- 分块转移时,右端点最多变化,分块共有个,复杂度为
- 分块相同时,左端点最多变化,分块转移时,左端点最多变化,共有个询问,复杂度为
所有总时间复杂度就是
0x02 树上莫队
树上莫队有一种树分块的做法这里不讲(因为我不会。。。),有兴趣可以看看vfk的博客WC 2013 糖果公园 park 题解。
还有一种就是先把一棵树变成一条序列,然后直接用莫队做就可以了,比如说下面这棵树
我们把它整理成括号序的形式为:521134432665(也就是在dfs遍历树的时候,将每个结点进栈时记录一次,出栈时记录一次)。
如果要询问之间的信息,需要分类讨论:
- 如果是的祖先,所求信息即为最后出现位置之间的信息。
- 如果不是的祖先,所求信息即为最先出现的位置以及最后出现的位置之间的信息再加上上的信息。
注意有些节点可能会在括号序中出现两次,说明这个节点在这段过程中入栈后又弹出了,不能计入所求信息(处理的话开个数组异或一下就好了)。
比如说要求之间的信息,这一段信息为 括号序 4326 再加上。
然后把剩下的事情交给莫队……
0x03 带修改的莫队
对三元组进行排序,表示在询问之前已经进行了次修改操作, 同理知道了 ,我们就可以知道 , , , , , 的情况,分块大小设为,总时间复杂度为。
(证明略)
0x04 草丛里的莫队
待更,这个版本插了真眼也看不到。(摊手)
0x05 题目&代码
Problem 2038. -- [2009国家集训队]小Z的袜子(hose)
大意是询问区间内任意选两个数为同一个数的概率并化为最简分数。
设在某一区间内共有颜色,每双袜子的个数为
答案为
化简
即
所以只需要用莫队处理每个区间内不同数字的平方和就好了
#include <bits/stdc++.h>
using namespace std; const int Maxn = 50005; typedef long long ll; inline ll sqr(const ll &x) { return x * x; } inline ll gcd(const ll &a, const ll &b) { if (!b) return a; else return gcd(b, a % b); } inline char get(void) { static char buf[1000000], *p1 = buf, *p2 = buf; if (p1 == p2) { p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin); if (p1 == p2) return EOF; } return *p1++; } inline void read(int &x) { x = 0; static char c; for (; !(c >= '0' && c <= '9'); c = get()); for (; c >= '0' && c <= '9'; x = x * 10 + c - '0', c = get()); } int belong[Maxn]; // 每个点的分块预处理 ll ans1[Maxn], ans2[Maxn]; struct Cmd { int l, r, id; friend bool operator < (const Cmd &a, const Cmd &b) { if (belong[a.l] == belong[b