莫队算法

来源:互联网 发布:淘宝项链店铺 编辑:程序博客网 时间:2024/05/14 23:58

满足以下两个条件的即可使用莫队算法:

1. 只有查询没有修改

2. 可由[l, r] O(1)或O(lgn) 推出[l + 1, r], [l - 1, r], [l, r - 1], [l, r + 1]

复杂度即为O(n ^ 1.5)或O(n ^ 1.5 * lgn)


莫队算法是将询问按左端点所在块作为第一关键字,右端点作为第二关键字排序。

莫队算法非常容易实现,只需排好序顺着计算就好了。关键是复杂度证明。


先大致说下实现过程。

例题: bzoj2038 [2009国家集训队] 小z的袜子(house)

题目大意:

长度为n的序列代表n只袜子,每只袜子有颜色ci(不分左右,型号,只关注颜色),m个询问,询问区间内抽到一双相同颜色袜子的概率。

题解:

无查询;记录区间内抽到一双相同颜色袜子的排列数,如果知道[l, r]的答案,

可以通过nowans -= cnt[r + 1] * (cnt[r + 1] - 1);++cnt[r + 1]; nowans += cnt[r + 1] * (cnt[r + 1] - 1); O(1)求得[l, r + 1]的答案。[l + 1, r], [l - 1, r], [l, r - 1]同理。其中cnt[x]表示当前区间内颜色x的个数。

故可使用莫队。

假设一开始我们找到一个区间的答案(比如[1, 0])。对于m个询问,我们按莫队算法描述的顺序排序,按照这个顺序依次用第i个答案推出第i+1个答案。每一步操作数都是| l' - l | + | r' - r |。

主要代码:

void updata(int x, int y) {    now -= (ll)cnt[x] * (cnt[x] - 1);    cnt[x] += y;    now += (ll)cnt[x] * (cnt[x] - 1);}

for (int i = 0, l = 1, r = 0; i < m; ++i) {    while (r < q[i].r) updata(c[++r], 1);    while (r > q[i].r) updata(c[r--], -1);    while (l > q[i].l) updata(c[--l], 1);    while (l < q[i].l) updata(c[l++], -1);    if (now == 0) {        a[q[i].id] = 0;        b[q[i].id] = 1;    }    else {        a[q[i].id] = now;        b[q[i].id] = (ll)(q[i].r - q[i].l + 1) * (q[i].r - q[i].l);        ll tmp = __gcd(a[q[i].id], b[q[i].id]);        a[q[i].id] /= tmp;        b[q[i].id] /= tmp;    }}
下面给出粗略证明。由于n, m同一级别,所以计算复杂度时都用n来表示。


总共分成了n ^ 0.5块,我们不妨假设这n ^ 0.5块每块的第一个都是暴力算出来的。暴力算一个区间[l, r]是O(n),总共是O(n ^ 1.5)。

然后在每块内分别考虑左端点和右端点。

左端点:

同一块之间左端点相差不超过n ^ 0.5,所以每次最多移动n ^ 0.5次,总共需计算n个询问,复杂度是O(n ^ 1.5)。

右端点:

在同一块里,右端点递增,最多移动n次,总共有n ^ 0.5块, 所以移动n ^ 1.5次。复杂度也是O(n ^ 1.5)。

综上,总复杂度O(n ^ 1.5)。


练习题:hdu5145 NPY and girls

0 0