莫队算法入门详细讲解(MoQueue)

莫队是基于分块从而诞生出来的一种技巧(我是这样理解的…)
主要用于离线处理查询区间的问题,要求会基本的分块操作
具体操作为保存所有的询问,然后对于询问进行分处理,之后遍历所有的询问,对于每次询问的区间端点与上一次的端点,进行 d e l del a d d add 来使其重叠吻合(从而达到一种前缀和的感觉??)
处理查询的复杂度是 n s q r t ( 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 数组,代表莫队此时维护的区间每个数的个数,初始化当然全都是0,同时开两个变量l和r代表此时维护的区间,然后因为莫队有一定递推的感觉,所以我们开一个 r e s 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 函数与 a d d add 函数,
我们对于当前的询问,所需要的范围是 q [ i ] . l q[i].l q [ i ] . r q[i].r ,我们已知范围 l l r r 的答案,我们只需要在原有答案上进行修改即可,关于怎么修改再说,我们先看上面的四个 w h i l e while ,再解释函数的实现
前提是上一次询问 l l r r 的答案是 r e s res ,
然后如果当前的 l l 在目标 q [ i ] . l q[i].l 左边,那么我们需要消除 l l 位置的数的贡献并使 l l 右移,也就是 d e l ( a [ l + + ] ) del(a[l ++]) ,如果在右边,那我们就需要使得 l l 左移并添加新产生的 l l 位置的数的贡献,也就是 a d d ( a [ l ] ) add(a[-- l])
关于自增自减运算符放前面还是放后面一定要好好想一下

注意我们需改区间的时候,是需要 添加/删除 这个位置的数的贡献,所以>我们进行修改传参的是这个位置的元素的大小

右端点同理就不再赘述了,一定要理解上面这段话,没理解就多读几遍
---------------------
如果你理解了上面那段话,我们就来开始看函数的具体实现
不同的题函数有不同的写法,但是大体是一样的,比如这个题需要统计 c n t [ x ] cnt[x] 的个数的平方,那假如我们要删掉一个 x x ,这个时候就需要用到前文所说的 c n t cnt 数组了,我们先用上一次的答案 a n s ans 减去所有的 x x 的数目的贡献,也就是

ans -= cnt[x] * cnt[x];  因为题目要求的是平方,所以这里平方了

剪掉所有 x x 的贡献之后,我们再修改 c n t [ x ] cnt[x] 的值,进行相应的加/减操作,在这里就是

-- cnt[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;
}

猜你喜欢

转载自blog.csdn.net/leoxe/article/details/107587718