普通莫队算法
形式¶
假设
实现¶
离线后排序,顺序处理每个询问,暴力从上一个区间的答案转移到下一个区间答案(一步一步移动即可)。
排序方法¶
对于区间
模板¶
inline void move(int pos, int sign) {
// update nowAns
}
void solve() {
BLOCK_SIZE = int(ceil(pow(n, 0.5)));
sort(querys, querys + m);
for (int i = 0; i < m; ++i) {
const query &q = querys[i];
while (l > q.l) move(--l, 1);
while (r < q.r) move(r++, 1);
while (l < q.l) move(l++, -1);
while (r > q.r) move(--r, -1);
ans[q.id] = nowAns;
}
}
复杂度分析¶
以下的情况在
首先是分块这一步,这一步的时间复杂度是
接着就到了莫队算法的精髓了,下面我们用通俗易懂的初中方法来证明它的时间复杂度是
证:令每一块中
由第一次排序可知,
显然,对于每一块暴力求出第一个询问的时间复杂度为
考虑最坏的情况,在每一块中,
考虑
重点分析
将每一块
对于
(裂项求和)
由题可知
综上所述,莫队算法的时间复杂度为
但是对于
怎么分块呢?
我们设块长度为
事实上,如果块长度的设定不准确,则莫队的时间复杂度会受到很大影响。例如,如果
莫队算法看起来十分暴力,很大程度上是因为莫队算法的分块排序方法看起来很粗糙。我们会想到通过看上去更精细的排序方法对所有区间排序。一种方法是把所有区间
假设
莫队算法还有一个特点:当
例题 & 代码¶
例题「国家集训队」小 Z 的袜子
题目大意:
有一个长度为
思路:莫队算法模板题。
对于区间
然后从序列的第一个询问开始计算答案,第一个询问通过直接暴力算出,复杂度为
具体做法:
对于区间
我们设
而这个询问的答案就是
这里有个优化:
所以
所以
算法总复杂度:
下面的代码中 deno
表示答案的分母 (denominator),nume
表示分子 (numerator),sqn
表示块的大小:arr
是输入的数组,node
是存储询问的结构体,tab
是询问序列(排序后的),col
同上所述。
注意:由于 ++l
和 --r
的存在,下面代码中的移动区间的 4 个 while 循环的位置很关键,不能随意改变它们之间的位置关系。
关于四个循环位置的讨论
莫队区间的移动过程,就相当于加入了
- 对于
l\le r [1,l-1] [l,r] [r+1,+\infty) - 对于
l=r+1 [1,r] [r+1,+\infty) - 对于
l>r+1 [r+1,l-1]
因此,如果某时刻出现 set
维护区间中的所有数,就会出现“需要删除 set
中不存在的元素”的问题。
代码中的四个 while 循环一共有
循环顺序 | 正确性 | 反例或注释 |
---|---|---|
l--,l++,r--,r++ | 错误 | |
l--,l++,r++,r-- | 错误 | |
l--,r--,l++,r++ | 错误 | |
l--,r--,r++,l++ | 正确 | 证明较繁琐 |
l--,r++,l++,r-- | 正确 | |
l--,r++,r--,l++ | 正确 | |
l++,l--,r--,r++ | 错误 | |
l++,l--,r++,r-- | 错误 | |
l++,r++,l--,r-- | 错误 | |
l++,r++,r--,l-- | 错误 | |
l++,r--,l--,r++ | 错误 | |
l++,r--,r++,l-- | 错误 |
全部 24 种排列中只有 6 种是正确的,其中有 2 种的证明较繁琐,这里只给出其中 4 种的证明。
这 4 种正确写法的共同特点是,前两步先扩大区间(l--
或 r++
),后两步再缩小区间(l++
或 r--
)。这样写,前两步是扩大区间,可以保持
参考代码
#include <algorithm>
#include <cmath>
#include <cstdio>
using namespace std;
const int N = 50005;
int n, m, maxn;
int c[N];
long long sum;
int cnt[N];
long long ans1[N], ans2[N];
struct query {
int l, r, id;
bool operator<(const query &x) const {
if (l / maxn != x.l / maxn) return l < x.l;
return (l / maxn) & 1 ? r < x.r : r > x.r;
}
} a[N];
void add(int i) {
sum += cnt[i];
cnt[i]++;
}
void del(int i) {
cnt[i]--;
sum -= cnt[i];
}
long long gcd(long long a, long long b) { return b ? gcd(b, a % b) : a; }
int main() {
scanf("%d%d", &n, &m);
maxn = sqrt(n);
for (int i = 1; i <= n; i++) scanf("%d", &c[i]);
for (int i = 0; i < m; i++) scanf("%d%d", &a[i].l, &a[i].r), a[i].id = i;
sort(a, a + m);
for (int i = 0, l = 1, r = 0; i < m; i++) {
if (a[i].l == a[i].r) {
ans1[a[i].id] = 0, ans2[a[i].id] = 1;
continue;
}
while (l > a[i].l) add(c[--l]);
while (r < a[i].r) add(c[++r]);
while (l < a[i].l) del(c[l++]);
while (r > a[i].r) del(c[r--]);
ans1[a[i].id] = sum;
ans2[a[i].id] = (long long)(r - l + 1) * (r - l) / 2;
}
for (int i = 0; i < m; i++) {
if (ans1[i] != 0) {
long long g = gcd(ans1[i], ans2[i]);
ans1[i] /= g, ans2[i] /= g;
} else
ans2[i] = 1;
printf("%lld/%lld\n", ans1[i], ans2[i]);
}
return 0;
}
普通莫队的优化¶
我们看一下下面这组数据
// 设块的大小为 2 (假设)
1 1
2 100
3 1
4 100
手动模拟一下可以发现,r 指针的移动次数大概为 300 次,我们处理完第一个块之后,
什么是奇偶化排序?奇偶化排序即对于属于奇数块的询问,r 按从小到大排序,对于属于偶数块的排序,r 从大到小排序,这样我们的 r 指针在处理完这个奇数块的问题后,将在返回的途中处理偶数块的问题,再向 n 移动处理下一个奇数块的问题,优化了 r 指针的移动次数,一般情况下,这种优化能让程序快 30% 左右。
排序代码:
压行
// 这里有个小细节等下会讲
int unit; // 块的大小
struct node {
int l, r, id;
bool operator<(const node &x) const {
return l / unit == x.l / unit
? (r == x.r ? 0 : ((l / unit) & 1) ^ (r < x.r))
: l < x.l;
}
};
不压行
struct node {
int l, r, id;
bool operator<(const node &x) const {
if (l / unit != x.l / unit) return l < x.l;
if ((l / unit) & 1)
return r <
x.r; // 注意这里和下面一行不能写小于(大于)等于,否则会出错(详见下面的小细节)
return r > x.r;
}
};
Warning
小细节:如果使用 sort 比较两个函数,不能出现
对于压行版,如果没有 r == x.r
的特判,当 l 属于同一奇数块且 r 相等时,会出现上面小细节中的问题(自己手动模拟一下),对于压行版,如果写成小于(大于)等于,则也会出现同样的问题。
参考资料¶
buildLast update and/or translate time of this article,Check the history
editFound smelly bugs? Translation outdated? Wanna contribute with us? Edit this Page on Github
peopleContributor of this article StudyingFather, Backl1ght, countercurrent-time, Ir1d, greyqz, MicDZ, ouuan
translateTranslator of this article Visit the original article!
copyrightThe article is available under CC BY-SA 4.0 & SATA ; additional terms may apply.