莫队是基于分块从而诞生出来的一种技巧(我是这样理解的…)
主要用于离线处理查询区间的问题,要求会基本的分块操作
具体操作为保存所有的询问,然后对于询问进行分处理,之后遍历所有的询问,对于每次询问的区间端点与上一次的端点,进行 d e l del del和 a d d add add来使其重叠吻合(从而达到一种前缀和的感觉??)
处理查询的复杂度是 n ∗ s q r t ( n ) n*sqrt(n) n∗sqrt(n),复杂度证明点这里
基础的莫队还是很简单的,我10min就看懂了并当场一发AC了一个题
----------------------------------
---------------------------------
前面所说的可以没理解,可以慢慢看下面的解释
先从例题开始说吧,比如说这个题
暴力肯定是不行的…
既然我们说莫队是离线查询,那我们先开一个结构体储存读入的询问
struct node {
LL l, r, id;
}q[maxn];
l,r,id分别代表这次询问的左右区间和下标(即这个询问是哪一次的询问)
所以读入数据就是
原数组
rep(i,1,n) {
scanf("%lld", &a[i]);
}
查询
rep(i,1,m) {
scanf("%lld %lld", &q[i].l, &q[i].r);
q[i].id = i;
}
然后就是一个分块的基本操作,单独开个belong数组代表每个数所属的块,然后把区间分成块(这个学过分块就看得懂)
block = sqrt(n);// 每个块的大小
rep(i,1,n) {
belong[i] = (i - 1) / block + 1;
}
之后我们需要对查询进行排个序,按照所属分块的位置从左到右排,如果左端点在同一个块内,就优先把右端点最靠前的放在前面,这里我们重载一下结构体内运算符,那就是
struct node {
LL l, r, id;
这个优先级的设置就是上面所说的
bool operator < (const node& b) const {
if(belong[l] == belong[b.l]) {
return r < b.r;
} else {
return belong[l] < belong[b.l];
}
}
}q[maxn];
sort(q + 1, q + m + 1);
同时我们需要开一个 c n t cnt cnt数组,代表莫队此时维护的区间每个数的个数,初始化当然全都是0,同时开两个变量l和r代表此时维护的区间,然后因为莫队有一定递推的感觉,所以我们开一个 r e s res res变量代表上一次查询的答案,然后这一次的答案只需要在上一次的答案上进行修改就可以了
初始化维护的内容
memset(cnt, 0, sizeof(cnt));
l = 1, r = 0, res = 0;
然后就是莫队的精髓了,我们开始遍历来处理每个询问
先放全部代码,再说
rep(i,1,m) {
while(l < q[i].l) {
del(a[l ++]);
}
while(l > q[i].l) {
add(a[-- l]);
}
while(r > q[i].r) {
del(a[r --]);
}
while(r < q[i].r) {
add(a[++ r]);
}
ans[q[i].id] = res;
}
首先先不管 d e l del del函数与 a d d add add函数,
我们对于当前的询问,所需要的范围是 q [ i ] . l q[i].l q[i].l和 q [ i ] . r q[i].r q[i].r,我们已知范围 l l l和 r r r的答案,我们只需要在原有答案上进行修改即可,关于怎么修改再说,我们先看上面的四个 w h i l e while while,再解释函数的实现
前提是上一次询问 l l l到 r r r的答案是 r e s res res,
然后如果当前的 l l l在目标 q [ i ] . l q[i].l q[i].l的左边,那么我们需要消除 l l l位置的数的贡献并使 l l l右移,也就是 d e l ( a [ l + + ] ) del(a[l ++]) del(a[l++]),如果在右边,那我们就需要使得 l l l左移并添加新产生的 l l l位置的数的贡献,也就是 a d d ( a [ − − l ] ) add(a[-- l]) add(a[−−l])
关于自增自减运算符放前面还是放后面一定要好好想一下
注意我们需改区间的时候,是需要 添加/删除 这个位置的数的贡献,所以>我们进行修改传参的是这个位置的元素的大小
右端点同理就不再赘述了,一定要理解上面这段话,没理解就多读几遍
---------------------
如果你理解了上面那段话,我们就来开始看函数的具体实现
不同的题函数有不同的写法,但是大体是一样的,比如这个题需要统计 c n t [ x ] cnt[x] cnt[x]的个数的平方,那假如我们要删掉一个 x x x,这个时候就需要用到前文所说的 c n t cnt cnt数组了,我们先用上一次的答案 a n s ans ans减去所有的 x x x的数目的贡献,也就是
ans -= cnt[x] * cnt[x]; 因为题目要求的是平方,所以这里平方了
剪掉所有 x x x的贡献之后,我们再修改 c n t [ x ] cnt[x] cnt[x]的值,进行相应的加/减操作,在这里就是
-- cnt[x];
然后我们再加上新的 x x x的数量的贡献
ans += cnt[x] * cnt[x];
所以对于删除操作总体就是
void del(LL x) {
res -= cnt[x] * cnt[x];
-- cnt[x];
res += cnt[x] * cnt[x];
}
同理添加操作就是
void add(LL x) {
res -= cnt[x] * cnt[x];
++ cnt[x];
res += cnt[x] * cnt[x];
}
如果你真的明白了删除的操作,那么添加的操作不用我说想必你也懂了,那就完结撒花咯(
记得点赞
附赠完整代码,码风不好
const int maxn = 5e4 + 7;
const int maxm = 2e6 + 7;
LL n, m, k, block;
LL belong[maxn], a[maxn], ans[maxn], res, cnt[maxn];
struct node {
LL l, r, id;
bool operator < (const node& b) const {
if(belong[l] == belong[b.l]) {
return r < b.r;
} else {
return belong[l] < belong[b.l];
}
}
}q[maxn];
void add(LL x) {
res -= cnt[x] * cnt[x];
++ cnt[x];
res += cnt[x] * cnt[x];
}
void del(LL x) {
res -= cnt[x] * cnt[x];
-- cnt[x];
res += cnt[x] * cnt[x];
}
void solve (int& kase) {
ms(cnt, 0);
scanf("%lld %lld %lld", &n, &m, &k);
block = sqrt(n);
rep(i,1,n) {
scanf("%lld", &a[i]);
belong[i] = (i - 1) / block + 1;
}
rep(i,1,m) {
scanf("%lld %lld", &q[i].l, &q[i].r);
q[i].id = i;
}
sort(q + 1, q + m + 1);
LL l = 1, r = 0;
res = 0;
rep(i,1,m) {
while(l < q[i].l) {
del(a[l ++]);
}
while(l > q[i].l) {
add(a[-- l]);
}
while(r > q[i].r) {
del(a[r --]);
}
while(r < q[i].r) {
add(a[++ r]);
}
ans[q[i].id] = res;
}
rep(i,1,m) {
printf("%lld\n", ans[i]);
}
}
int main () {
int test = 1, kase = 0;
while(test --) {
solve(kase);
}
return 0;
}