[学习笔记]省选算法·莫队

来源:互联网 发布:ios未越狱修改数据 编辑:程序博客网 时间:2024/06/06 03:57

一、开头

神犇EX:你是哪里来的弱菜?你是不是上次NOIP爆0的那个大弱鸡xyz32768?
xyz32768:我就是那个NOIP2017写炸两题的蒟蒻,的确是大弱鸡。
神犇EX:我还听说你连莫队都不会……你恐怕明年NOIP连400都没有了。
xyz32768:莫……莫队?
神犇EX:就是这样:一个n个数的序列,每次询问区间……
xyz32768:反正我也不懂,算了,我已经做好了NOIP2018爆0的准备了。

二、引入

一个问题:一个n个数的序列,每次询问区间[l,r]内出现了多少个不同的数。
一个想法是线段树,但是也很容易看出,这里线段树上维护的内容没有合并性质,因此线段树不可做。而对于区间的内容没有合并性质的问题,可以用下面介绍的莫队算法解决。

三、莫队算法的条件

1、不包含修改操作。
2、题目允许离线,也就是允许在所有询问全部读入完之后回答所有询问。
3、不同区间的结果可以互相计算得出。怎么理解这个条件呢?
就上面的问题而言,如果上一次已经回答了[l,r]区间的答案,并且已经存下了[l,r]区间里的所有数值的出现次数,那么如果下面要询问[l,r+1]的结果,就只要把右指针r向右移一个单位,并将序列的第r+1个数的出现次数++,同时维护当前的答案,也就是说,如果第r+1个数在[l,r]区间内没有出现,则当前答案++。同样,对于[l,r1],[l1,r][l+1,r]以及其他任何的区间都可以在上一个询问的基础上,通过lr移动指针来求得下一个询问的答案。如果满足这样的条件,就是说不同区间的结果可以互相计算得出。

四、莫队算法的流程

可以看出,一次移动指针是O(1)的。于是想到可以回答第1个询问之后,不断地移动指针,一个一个移动到后面将要回答的所有区间。
莫队算法的主要思想就是这样。同时,莫队算法利用了可以离线的条件,将询问按照合理的顺序进行求解,实现了O(nn)的复杂度。
首先,将序列分块,即分成n块,每个块的大小为n
然后,就将询问按照左端点所在的块为第一关键字,右端点的位置为第二关键字进行从小到大排序,这样,就能像上面那样不断移动指针,可以达到O(nn)的复杂度。

五、复杂度证明

不妨把左端点在同一个块内的询问分成一组。
先考虑右端点的移动次数。由于在同一组询问内的右端点是递增的,所以在同一组内,右端点移动了O(n)次。同时在跨越两个组时,右端点的移动次数也是O(n),即右端点一共移动了O(nn)次。
再考虑左端点的移动次数。可以看出,在同一组询问内,左端点一次移动的次数为O(n)次。
再加上在跨越两个组时,左端点的移动次数也是O(n),因此左端点一共移动了O(nn)次。复杂度得证。

六、BZOJ 1878代码

#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 5e4 + 5, M = 1e6 + 5, L = 2e5 + 5;int n, a[N], ans[N], cnt[M];struct cyx {    int l, r, bl, id;    inline bool operator < (cyx b) const        {return bl < b.bl || (bl == b.bl && r < b.r);}} ask[L];int main() {    int i, m, S; scanf("%d", &n); S = sqrt(n);    for (i = 1; i <= n; i++) scanf("%d", &a[i]);    scanf("%d", &m); for (i = 1; i <= m; i++) {        scanf("%d%d", &ask[i].l, &ask[i].r);        ask[i].id = i; ask[i].bl = (ask[i].l - 1) / S + 1;    }    sort(ask + 1, ask + m + 1);    int tot = 0, sl = 0, sr = 0;    for (i = 1; i <= m; i++) {        int l = ask[i].l, r = ask[i].r;        while (sl < l) if (!--cnt[a[sl++]]) tot--;        while (sl > l) if (!cnt[a[--sl]]++) tot++;        while (sr < r) if (!cnt[a[++sr]]++) tot++;        while (sr > r) if (!--cnt[a[sr--]]) tot--;        ans[ask[i].id] = tot;    }    for (i = 1; i <= m; i++) printf("%d\n", ans[i]);    return 0;}

七、题目

1、[BZOJ1878][SDOI2009]HH的项链:
http://www.lydsy.com/JudgeOnline/problem.php?id=1878
2、[BZOJ2038][2009国家集训队]小z的袜子:
http://www.lydsy.com/JudgeOnline/problem.php?id=2038
3、[BZOJ3236][AHOI2013]作业:
http://www.lydsy.com/JudgeOnline/problem.php?id=3236
4、[BZOJ4540][HNOI2016]序列:
http://www.lydsy.com/JudgeOnline/problem.php?id=4540
5、[BZOJ4542][HNOI2016]大数:
http://www.lydsy.com/JudgeOnline/problem.php?id=4542

原创粉丝点击