超全的莫队算法一遍过

这篇具有很好参考价值的文章主要介绍了超全的莫队算法一遍过。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

        前置知识:双指针、分块

        简单概括而言,莫队是一个离线算法,核心思想是将所有的询问按照分块进行排序后,对于每个询问可以通过双指针增删数据来达到总体的复杂度。

        莫队通常用来解决求一段区间内不同数的个数相关的问题。

目录

1、莫队基础

附:简单优化

2、莫队的修改

3、莫队回滚

4、树上莫队

5、莫队二次离线


1、莫队基础

来看一道题目:(洛谷P1972)

HH 有一串由各种漂亮的贝壳组成的项链。HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含义。HH 不断地收集新的贝壳,因此,他的项链变得越来越长。

有一天,他突然提出了一个问题:某一段贝壳中,包含了多少种不同的贝壳?这个问题很难回答…… 因为项链实在是太长了。于是,他只好求助睿智的你,来解决这个问题。

        观察题意可知,这是一个区间查询问题,查询的是区间内不同颜色的数量。如果用暴力算法将一个区间的颜色出现的次数存在一个数组cnt里,每次判断该节点是否是第一次出现,最后统计总结果的复杂度为

        考虑优化,将询问的区间按结束的点进行排序,用双指针i,j分别表示左端点和右端点

莫队,# 算法学习笔记,c++,算法,学习,数据结构

设上一次查询的区间为,本次查询的区间为, 那么当我们将点移动到点的时候,本质上是删掉一些节点,那么节点颜色出现次数cnt和颜色总数res有以下关系

 而当我们将移动到时,本质上是增加一些节点,那么cnt与res的关系是

莫队,# 算法学习笔记,c++,算法,学习,数据结构

        当然这是显而易见的,和纯暴力没什么两样,这样的时间复杂度依旧是,如何进行进一步的优化呢?

        可以利用我们学过的分块思想,如果我们把所有的询问区间存下来,然后对左端点按块排序,当两个询问左端点在同一块内时再看右端点的先后顺序。排序的复杂度为

莫队,# 算法学习笔记,c++,算法,学习,数据结构

 如图,如此一来,每两个询问区间的转移复杂度为

1、左端点的块内查询复杂度为

2、左端点的块间复杂度最大为

3、右端点的复杂度最大为

        最后的总时间复杂度为莫队,# 算法学习笔记,c++,算法,学习,数据结构,如果m和n近似相等,那么可以简化为

        这个方法就是莫队。

附:简单优化

(注意,这两种优化实际复杂度提升并不明显,只适合对卡常的题目进行优化)

(1)、奇偶块排序

        根据莫队指针移动的顺序我们可以观察到,当本次查询与上次查询的左端点在不同块内时,右指针需要先移动到左指针的位置,然后重新开始向右移动,那么我们可以对下标为奇数的块正序排列右端点,下标为偶数的块倒序排列右端点,这样一来右指针就可以少走一次区间,稍微提高一点速度。

(2)、分块的长度

        由于我们推理复杂度的时候默认将询问次数和区间长度的数量级统一为n,但如果询问次数m远大于区间总长度n,那么莫队的复杂度将不再近似为,设分块长度为len,那么复杂度为莫队,# 算法学习笔记,c++,算法,学习,数据结构,为了使这个式子最小,可以求得,那么我们将区间改为该式即可。

附上代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 50010, M = 200010, S = 1000010;

int n, m, len;
int w[N], ans[M];
struct Query
{
    int id, l, r;
}q[M];
int cnt[S];

int get(int x)
{
    return x / len;
}

bool cmp(const Query& a, const Query& b)
{
    int i = get(a.l), j = get(b.l);
    if (i != j) return i < j;
    return a.r < b.r;
}

void add(int x, int& res)
{
    if (!cnt[x]) res ++ ;
    cnt[x] ++ ;
}

void del(int x, int& res)
{
    cnt[x] -- ;
    if (!cnt[x]) res -- ;
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
    scanf("%d", &m);
    len = max(1, (int)sqrt((double)n * n / m));

    for (int i = 0; i < m; i ++ )
    {
        int l, r;
        scanf("%d%d", &l, &r);
        q[i] = {i, l, r};
    }
    sort(q, q + m, cmp);

    for (int k = 0, i = 0, j = 1, res = 0; k < m; k ++ )
    {
        int id = q[k].id, l = q[k].l, r = q[k].r;
        while (i < r) add(w[ ++ i], res);
        while (i > r) del(w[i -- ], res);
        while (j < l) del(w[j ++ ], res);
        while (j > l) add(w[ -- j], res);
        ans[id] = res;
    }

    for (int i = 0; i < m; i ++ ) printf("%d\n", ans[i]);
    return 0;
}

2、莫队的修改

来看一条题目(洛谷P1903):莫队,# 算法学习笔记,c++,算法,学习,数据结构

        观察到普通的莫队是无法进行修改的,因为修改存在先后顺序,但是我们可以通过在数组上建立一个新的时间轴,按照时间顺序存储每一次修改之后的值,如图所示莫队,# 算法学习笔记,c++,算法,学习,数据结构

        观察到如果时间戳1的下一个操作是2,就要判断修改节点(红色)是否在指针移动区间内,如果不在,就不用管,如果在,就需要更改这些操作。

        注意,询问排序并不严格按时间戳排序,时间戳的作用仅仅是存储修改节点的先后顺序,严格来说,询问应当按照左端点所在块进行排序,同块时按照右端点所在块排序,如果还是一样才看时间戳的排序,所以会经常在时间戳上来回移动,可能一会上去了,一会又下来了,上去的时候把值改过去,下来的时候又要改回来。因此如果直接更改询问区间内修改的值,那么在回来的时候就找不到原来的数是什么了。

        这里可以用一个小技巧,我们可以把所有的修改操作存在一个数组里,当修改进行时,只需要将当前的数和修改数组里的数进行交换即可,那么在改回来的时候再次交换就又换回了原来的数。

        最后再计算一下时间复杂度方便计算分块的最优大小

设数组长度为n,询问次数m,分块大小为a,修改次数t

左端点移动:块内,块间,总莫队,# 算法学习笔记,c++,算法,学习,数据结构

右端点移动:块内,块间,总莫队,# 算法学习笔记,c++,算法,学习,数据结构

时间戳t移动:次数t * 左端点n / a * 右端点n / a ,

分情况讨论a:

① 

总复杂度约为

②  

总复杂度约为莫队,# 算法学习笔记,c++,算法,学习,数据结构

 

总复杂度约为

很明显a大概在左右

观察题目所给复杂度

莫队,# 算法学习笔记,c++,算法,学习,数据结构  n,m复杂度近似,可以都用n代替,那么左右端点的复杂度约为

t的复杂度大约为

为使复杂度平均,将两式相等

        

解得

所以我们的区间长度 len 取 cbrt(n * t) 即可。

最后附上代码:

include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 2e6, M = 1e6 + 10;

struct Query {
    int id, l, r, t;
}q[N];
struct Modify {
    int p, c;
}c[N];
int n, m, w[N], cnt[M];
int mq, mc, len;
int ans[N];

int get(int x) {
    return x / len;
}

bool cmp(const Query& a, const Query& b) {
    int al = get(a.l), bl = get(b.l);
    int ar = get(a.r), br = get(b.r);
    if (al != bl) return al < bl;
    if (ar != br) return ar < br;
    return a.t < b.t;
}

void add(int x, int& res) {
    if (!cnt[x]) res++;
    cnt[x]++;
}

void del(int x, int& res) {
    cnt[x]--;
    if (!cnt[x]) res--;
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%d", &w[i]);
    for (int i = 0; i < m; i++) {
        char op[2];
        int a, b;
        scanf("%s%d%d", op, &a, &b);
        if (*op == 'Q') mq++, q[mq] = {mq, a, b, mc};
        else c[++mc] = {a, b};
    }
    len = cbrt((double)n * max(1, mc)) + 1;
    sort(q + 1, q + mq + 1, cmp);
    int res = 0;

    for (int i = 0, j = 1, t = 0, k = 1; k <= mq; k++) {
        int id = q[k].id, l = q[k].l, r = q[k].r, tm = q[k].t;
        while (i < r) add(w[++i], res);
        while (i > r) del(w[i--], res);
        while (j < l) del(w[j++], res);
        while (j > l) add(w[--j], res);
        while (t < tm) {
            t++;
            int p = c[t].p;
            if (p >= j && p <= i)
            {
                del(w[p], res);
                add(c[t].c, res);
            }
            swap(w[p], c[t].c);
        }
        while (t > tm) {
            int p = c[t].p;
            if (p >= j && p <= i)
            {
                del(w[p], res);
                add(c[t].c, res);
            }
            swap(w[p], c[t].c);
            t--;
        }
        ans[id] = res;
    }
    for (int i = 1; i <= mq; i++) cout << ans[i] << endl;
    return 0;
}

 3、莫队回滚

 前两问可以发现莫队很容易维护出现次数,即桶数组cnt,但是如果碰到这题:(JOISC2014)莫队,# 算法学习笔记,c++,算法,学习,数据结构        同样使用双指针的移动来更新询问区间,要维护一个区间内的最大值,加入很容易考虑,假设加入的数为 x,维护 res = max(res, x) 即可,但是如何删除一个数来更新res?

        回滚莫队的核心思想就是只进行添加操作,不进行删除操作,用回滚的方法代替删除一段区间来转移指针。

        我们用几张图来演示莫队是如何回滚的:

首先将询问区间按照左端点所在块进行排序

莫队,# 算法学习笔记,c++,算法,学习,数据结构

(如图,设红色为上一次询问,蓝色线段为当前询问)

 设当前左端点所在分块为B

1、如果 B 与上一个询问的 B' 不同,就将莫队左指针 j 设为 R[B] + 1,右指针 i 设为 R[B],此时区间为空莫队,# 算法学习笔记,c++,算法,学习,数据结构

2、如果询问区间的左右端点在同一分块内,那么直接暴力,时间在 ;

否则先将 i 扩展到右端点(由于在同一分块内,右指针按升序排列,所以 i 只会向一个方向走)直接加点即可,然后将 j 暴力加点到左端点,得到的结果保存;莫队,# 算法学习笔记,c++,算法,学习,数据结构

4、减去第3步的所有点,为下一次询问做准备。莫队,# 算法学习笔记,c++,算法,学习,数据结构

        核心操作是第四步,根据询问区间排序的规则我们知道:在同一分块内,右指针按升序排列,所以 i 只会向一个方向移动,不用考虑删除点的操作,但是左端点是乱序的,我们并不知道上一次询问的左端点在当前询问左端点的左边还是右边,所以干脆直接暴力加上这一段数,反正区间长度最长也就。

        这样一来,我们只需要每次添加询问区间左端点即可,用回滚的方式代替了删除。

附上代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>

using namespace std;

typedef long long LL;
const int N = 100010;

int n, m, len;
int w[N], cnt[N];
LL ans[N];
struct Query
{
    int id, l, r;
}q[N];
vector<int> nums;

int get(int x)
{
    return x / len;
}

bool cmp(const Query& a, const Query& b)
{
    int i = get(a.l), j = get(b.l);
    if (i != j) return i < j;
    return a.r < b.r;
}

void add(int x, LL& res)
{
    cnt[x] ++ ;
    res = max(res, (LL)cnt[x] * nums[x]);
}

int main()
{
    scanf("%d%d", &n, &m);
    len = sqrt(n);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]), nums.push_back(w[i]);
    sort(nums.begin(), nums.end());
    nums.erase(unique(nums.begin(), nums.end()), nums.end());
    for (int i = 1; i <= n; i ++ )
        w[i] = lower_bound(nums.begin(), nums.end(), w[i]) - nums.begin();

    for (int i = 0; i < m; i ++ )
    {
        int l, r;
        scanf("%d%d", &l, &r);
        q[i] = {i, l, r};
    }
    sort(q, q + m, cmp);

    for (int x = 0; x < m;)
    {
        int y = x;
        while (y < m && get(q[y].l) == get(q[x].l)) y ++ ;
        int right = get(q[x].l) * len + len - 1;

        // 暴力求块内的询问
        while (x < y && q[x].r <= right)
        {
            LL res = 0;
            int id = q[x].id, l = q[x].l, r = q[x].r;
            for (int k = l; k <= r; k ++ ) add(w[k], res);
            ans[id] = res;
            for (int k = l; k <= r; k ++ ) cnt[w[k]] -- ;
            x ++ ;
        }

        // 求块外的询问
        LL res = 0;
        int i = right, j = right + 1;
        while (x < y)
        {
            int id = q[x].id, l = q[x].l, r = q[x].r;
            while (i < r) add(w[ ++ i], res);
            LL backup = res;
            while (j > l) add(w[ -- j], res);
            ans[id] = res;
            while (j < right + 1) cnt[w[j ++ ]] -- ;
            res = backup;
            x ++ ;
        }
        memset(cnt, 0, sizeof cnt);
    }

    for (int i = 0; i < m; i ++ ) printf("%lld\n", ans[i]);
    return 0;
}

4、树上莫队

前三问都是一解决的一维数组,那么如果题目给了一棵树该怎么办:(洛谷SP10707)

莫队,# 算法学习笔记,c++,算法,学习,数据结构 样例给出的树是这样的莫队,# 算法学习笔记,c++,算法,学习,数据结构 这里介绍一个能够将一棵树转化成序列的方法:欧拉序列

欧拉序列就是从根节点开始进行一次dfs遍历,当第一次到达某个点时,将它记下来,当最后一次到达这个点离开时,再记录一次这个点,也就是将树上的所有点在按前序遍历和后序遍历分别存下来

void dfs(int u, int father)
{
    seq[ ++ top] = u;
    first[u] = top;
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j != father) dfs(j, u);
    }
    seq[ ++ top] = u;
    last[u] = top;
}

对于样例的树来说,它的欧拉序列为:

1 2 2 3 5 5 6 6 7 7 3 4 8 8 4 1

我们可以将某个数 i 第一次出现的位置记为 ,最后一次出现的位置记录为,那么对于两个询问的点x, y,假设 ,

(1)、LCA(x, y) = x

意为x是y的父节点,此时我们只需要取出欧拉序列中的[ fst[x], fst[y] ]这一段,不难看出,在x到y路径上的点只会在这一段中出现一次,其余的点要么出现两次,要么一次也不出现。

(2)、LCA(x, y) != x

我们可以取出[ lst[x], fst[y] ]这一段,那么同样的,在路径上的点只会出现一次。注意到x,y的最近公共祖先并不在这个序列里,因为搜索的顺序是lca(x,y) -> x -> y -> lca(x,y),所以我们要额外把lca(x, y)添加一遍

那么对于每次询问,我们可以用两个数组来维护信息,cnt依旧是结果出现的次数,st数组用来维护某个权值出现的奇偶性,因为0和2都是偶数,出现2次和出现一次一致,所以我们只需要在添加操作里将st的奇偶性取反,这样删除一个数和添加一个数就是等价操作了。

add函数:

void add(int x, int& res)
{
    st[x] ^= 1;
    if (st[x] == 0)
    {
        cnt[w[x]] -- ;
        if (!cnt[w[x]]) res -- ;
    }
    else
    {
        if (!cnt[w[x]]) res ++ ;
        cnt[w[x]] ++ ;
    }
}

 最后默写一遍LCA函数的模板即可,附上完整代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector>

using namespace std;

const int N = 100040, M = 1e5 + 10;

struct Query {
    int id, l, r, p;
}q[N];
int n, m, w[N], cnt[M], st[N], ans[M], len;
int e[M], ne[M], h[N], idx;
int seq[N], top, que[N], fst[N], lst[N];
int depth[N], fa[N][16];
vector<int> nums;

void add_edge(int a, int b) {
    e[idx] = b; ne[idx] = h[a]; h[a] = idx++;
    e[idx] = a; ne[idx] = h[b]; h[b] = idx++;
}

int get(int x) {
    return x / len;
}

void dfs(int u, int father) {
    seq[++top] = u;
    fst[u] = top;
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == father) continue;
        dfs(j, u);
    }
    seq[++top] = u;
    lst[u] = top;
}

void bfs() {
    memset(depth, 0x3d, sizeof depth);
    int hh = 0, tt = 0;
    que[0] = 1; depth[0] = 0; depth[1] = 1;
    while (hh <= tt) {
        int t = que[hh++];
        for (int i = h[t]; ~i; i = ne[i]) {
            int j = e[i];
            if (depth[j] > depth[t] + 1) {
                depth[j] = depth[t] + 1;
                fa[j][0] = t;
                for (int k = 1; k <= 15; k++) 
                    fa[j][k] = fa[fa[j][k - 1]][k - 1];
                que[++tt] = j;
            }
        }
    }
}

void add(int x, int &res) {
    st[x] ^= 1;
    if (st[x]) {
        if (!cnt[w[x]]) res++;
        cnt[w[x]]++;
    }
    else {
        cnt[w[x]]--;
        if (!cnt[w[x]]) res--;
    }
}

int lca(int a, int b) {
    if (depth[a] < depth[b]) swap(a, b);
    for (int i = 15; i >= 0; i--) {
        if (depth[fa[a][i]] >= depth[b])
            a = fa[a][i];
    }
    if (a == b) return a;
    for (int i = 15; i >= 0; i--) {
        if (fa[a][i] != fa[b][i]) {
            a = fa[a][i];
            b = fa[b][i];
        }
    }
    return fa[a][0];
}

bool cmp(const Query& a, const Query& b) {
    int i = get(a.l), j = get(b.l);
    if (i != j) return i < j;
    return a.r < b.r;
}

int main() {
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i++) scanf("%d", &w[i]), nums.push_back(w[i]);
    sort(nums.begin(), nums.end());
    nums.erase(unique(nums.begin(), nums.end()), nums.end());
    for (int i = 1; i <= n; i++) w[i] = lower_bound(nums.begin(), nums.end(), w[i]) - nums.begin();
    
    for (int i = 0; i < n - 1; i++) {
        int a, b;
        scanf("%d%d", &a, &b);
        add_edge(a, b);
    }
    dfs(1, -1);
    bfs();
    for (int i = 0; i < m; i++) {
        int a, b;
        scanf("%d%d", &a, &b);
        if (fst[a] > fst[b]) swap(a, b);
        int p = lca(a, b);
        if (p == a) q[i] = {i, fst[a], fst[b]};
        else q[i] = {i, lst[a], fst[b], p};
    }
    len = sqrt(top);
    sort(q, q + m, cmp);
    
    for (int k = 0, res = 0, i = 0, j = 1; k < m; k++) {
        int id = q[k].id, l = q[k].l, r = q[k].r, p = q[k].p;
        while (i < r) add(seq[++i], res);
        while (i > r) add(seq[i--], res);
        while (j < l) add(seq[j++], res);
        while (j > l) add(seq[--j], res);
        if (p) add(p, res);
        ans[id] = res;
        if (p) add(p, res);
    }
    for (int i = 0; i < m; i++) printf("%d\n", ans[i]);
    return 0;
}

5、莫队二次离线

        在前四章总我们每次移动莫队的左右指针的复杂度为O(1),因为只需要加入/删除即可,那么对于那些每次移动一格需要对整个序列重新维护的题目,我们不妨设每次移动的复杂度为f(i),那么莫队的总复杂度为,很明显是无法接受的,比如这一题:(洛谷P4887)

莫队,# 算法学习笔记,c++,算法,学习,数据结构

        我们可以设想当区间从移动到时,不管哪个指针向什么方向移动,都要统计区间内有多少数和它匹配,然后加上或删掉,两个指针移动的复杂度将会超过O(n),明显是无法接受的。

        莫队二次离线的思路就是将两个指针拆开操作,每次将其中一个指针看作定点,然后处理另一个指针移动的贡献值,最后合并即可。这样一来的时间复杂度会降为莫队,# 算法学习笔记,c++,算法,学习,数据结构

方法1:

可以假设:

1、 表示x在区间[l, r]内匹配数的数量

2、 表示区间1中的每个数在区间2中匹配的数量总和

3、 表示对于当前的询问,x在1到k - 1的区间中匹配的数量

4、由前缀和的知识可以知道

由于莫队左右指针移动有多种情况,我们这里先举一种情况进行分析,如图

莫队,# 算法学习笔记,c++,算法,学习,数据结构

我们将右指针固定,将移动到莫队,# 算法学习笔记,c++,算法,学习,数据结构位置,那么此时res应该

                                 

用4式变换得:    莫队,# 算法学习笔记,c++,算法,学习,数据结构

将移动到位置的总res变化为

                     莫队,# 算法学习笔记,c++,算法,学习,数据结构

对于该式, 可以直接用预处理差分数组后用O(1)算出 ;

部分可以将这个信息先记录下来,最后用扫描线扫一遍求出后在加进res。

随后对于左端点,右指针固定的方法与其一致,可以再推导一次式子:

                                莫队,# 算法学习笔记,c++,算法,学习,数据结构

                莫队,# 算法学习笔记,c++,算法,学习,数据结构

总res变化:

                        莫队,# 算法学习笔记,c++,算法,学习,数据结构

 同样的,第二个式子保存,之后通过扫描线求出后加入res,第一个式子直接求解即可。


方法二:

设表示前 i 项中与当前新加的数匹配的数量

设表示前 i 项中与 莫队,# 算法学习笔记,c++,算法,学习,数据结构匹配的数量

设表示当前(统计到i)能和x匹配的数量

莫队,# 算法学习笔记,c++,算法,学习,数据结构  

假设莫队右指针R想要向右移动一格,即R -> R + 1,那么结果应该增加

计算g数组

对于每一个数w[i],设与之匹配的数为x

则 x ^ w[i] = y,y是二进制有k个1的数。

移项得    x = y ^ w[i]

那么我们只需要把所有y找出来,存在一个nums数组里,每次计算g数组的时候直接暴力枚举即可。

总结:二次离线莫队代码不长,难于指针移动的推理,要仔细把握每一个移动的方向和符号,稍有不慎就要debug半天。。。。别问我为什么。。。这题是我夜里两点写完的。。。

最后附上代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>

using namespace std;

typedef long long LL;
const int N = 100010;

int n, m, k, len;
int w[N];
LL ans[N];
struct Query
{
    int id, l, r;
    LL res;
}q[N];
struct Range
{
    int id, l, r, t;
};
vector<Range> range[N];
int f[N], g[N];

inline int get_count(int x)
{
    int res = 0;
    while (x) res += x & 1, x >>= 1;
    return res;
}

inline int get(int x)
{
    return x / len;
}

bool cmp(const Query& a, const Query& b)
{
    int i = get(a.l), j = get(b.l);
    if (i != j) return i < j;
    return a.r < b.r;
}

int main()
{
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
    vector<int> nums;
    for (int i = 0; i < 1 << 14; i ++ )
        if (get_count(i) == k)
            nums.push_back(i);
    for (int i = 1; i <= n; i ++ )
    {
        for (auto y: nums) ++ g[w[i] ^ y];
        f[i] = g[w[i + 1]];
    }
    for (int i = 0; i < m; i ++ )
    {
        int l, r;
        scanf("%d%d", &l, &r);
        q[i] = {i, l, r};
    }
    len = sqrt(n);
    sort(q, q + m, cmp);

    for (int i = 0, L = 1, R = 0; i < m; i ++ )
    {
        int l = q[i].l, r = q[i].r;
        if (R < r) range[L - 1].push_back({i, R + 1, r, -1});
        while (R < r) q[i].res += f[R ++ ];
        if (R > r) range[L - 1].push_back({i, r + 1, R, 1});
        while (R > r) q[i].res -= f[ -- R];
        if (L < l) range[R].push_back({i, L, l - 1, -1});
        while (L < l) q[i].res += f[L - 1] + !k, L ++ ;
        if (L > l) range[R].push_back({i, l, L - 1, 1});
        while (L > l) q[i].res -= f[L - 2] + !k, L -- ;
    }

    memset(g, 0, sizeof g);
    for (int i = 1; i <= n; i ++ )
    {
        for (auto y: nums) ++ g[w[i] ^ y];
        for (auto& rg: range[i])
        {
            int id = rg.id, l = rg.l, r = rg.r, t = rg.t;
            for (int x = l; x <= r; x ++ )
                q[id].res += g[w[x]] * t;
        }
    }

    for (int i = 1; i < m; i ++ ) q[i].res += q[i - 1].res;
    for (int i = 0; i < m; i ++ ) ans[q[i].id] = q[i].res;
    for (int i = 0; i < m; i ++ ) printf("%lld\n", ans[i]);

    return 0;
}

6、bitset套莫队

暂时还没学,等我学会再更新(逃


写在最后:莫队小公式

莫队 = 双指针 + 分块

莫队修改 = 莫队 + 时间戳

回滚莫队 = 莫队 + 备份暴力

树上莫队 = 莫队 + 最近公共祖先

莫队二次离线 = 莫队 + 差分

你学费了吗?文章来源地址https://www.toymoban.com/news/detail-815207.html

到了这里,关于超全的莫队算法一遍过的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 超全的网络拓扑技术调研汇总

    结合上篇 网络拓扑 le5le-topology draw.io技术差异及研发方案对比汇总 闲暇之余又看了一写其他的开源框架,总之各有所长。以下是个人对其总结的一些优缺点,个人见解 如有不足及错误引导,欢迎各位指正留言。 1、Hightopo 官网地址 http://www.hightopo.com/demos/cn-index.html 效果: 优

    2024年02月12日
    浏览(33)
  • 品类超全的免费 API 大全整理

    AI 绘画:通过AI 生成图片,包括图生文、文生图等。 人脸检测:快速检测图片中的人脸并返回人脸位置,输出人脸关键点坐标,支持识别多张人脸。 静态活体检测:静态活体检测主要用于针对用户上传图像,返回该图像中的人脸是否为真人;基于图片中人像的破绽(摩尔纹

    2024年02月03日
    浏览(45)
  • 一个功能超全的 AI 翻译神器,开源了!

    公众号关注 “GitHubDaily” 设为 “星标”,每天带你逛 GitHub! 在大语言模型还没开始爆火的时候,GitHub 便已经陆续出现各种各样的 AI 应用,而机器翻译,可谓是其中被应用最为广泛的技术之一。 在 2020 年的时候,我曾给大家推荐过 GitHub 上一款功能超全的 AI 翻译神器: 团

    2024年02月06日
    浏览(52)
  • 史上超全的Halcon常用3D算子:点云处理

    在计算机视觉和机器人领域,点云处理是一项重要的任务。Halcon作为一款强大的图像处理软件,提供了丰富的3D算子来进行点云数据的处理和分析。本文将介绍一些常见的Halcon 3D算子,并提供相应的源代码示例。 读取点云数据 点云数据通常以文件的形式存在,可以通过Halco

    2024年02月02日
    浏览(52)
  • 超全的数据可视化大屏设计组件库 sketch格式

    随着大屏可视化设计需求的发展,可视化sketch矢量素材变得越来越受欢迎,它可以为设计师提供丰富的设计元素,帮助他们更高效更快速的完成设计任务。 大屏可视化sketch数量素材是B端可视化设计师们最佳设计资源,它可以帮助设计师轻松地创建出丰富的设计元素,如图像

    2024年02月11日
    浏览(41)
  • 【算法】莫队

    这篇博客起源于本人把一道 p o w ( 2 , n ) pow(2,n) p o w ( 2 , n ) 的问题考虑成求组合数前缀和的问题qwq,于是接触到了这个新算法来总结一下 参考自这篇文章,写得太好了 首先是一道模板题 题目意思是,给出一个数组a,再给出多个区间,问这些区间里分别有多少不一样的数字

    2024年02月07日
    浏览(33)
  • 超全的auto.js基础操作,目前是autoX.js的控制方式。2023年7月9日更新!(第2/4章)

    待办事项 登录界面 界面模板 用户调查 一个小测试 函数图像简单版 函数图像高级版 停止所有正在运行的脚本 运行脚本文件 运行录制文件 运行新的脚本任务 按钮控件 表格控件-内置图标查看器 复选框单选框控件 进度条控件 卡片布局 列表控件 时间日期选择控件 输入框控件

    2024年02月12日
    浏览(43)
  • 超全的auto.js基础操作,目前是autoX.js的控制方式。2023年7月9日更新!(第1/4章)

    点击左上角 拉出通知栏 三指捏合 三指下滑 双指捏合 心形手势 示例一 示例二 保存数组和复杂对象 保存整数登简单数据 随手记 打印常用传感器信息 显示常用传感器信息 定时执行 循环执行 菜单 单选框 多选框 简单计算器 模拟更新下载对话框 确认框 输入框 UI模式下使用对

    2024年02月10日
    浏览(41)
  • MySQL学习教程(超全)

    学习MySQL需要通过阅读文档、观看视频教程、参加在线课程等方式。同时,还需要进行实践操作,如创建数据库、表和数据插入、查询等操作,以加深对MySQL的理解和掌握。在实践中,也可以遇到一些问题,需要通过查询文档和搜索解决方案的方式来解决。 一、学习内容 1.学

    2024年02月14日
    浏览(38)
  • 机器学习超全数据集汇总

    我们都知道,在机器学习模型的测试过程中,数据集很重要。在构造数据集的时候,要注意做好数据的清洗和标注,一个高质量的数据集往往能够提高模型训练的质量和预测的准确率。在缺乏数据的情况下,可以尝试寻找一些公开数据集,特别是得到公认的被普遍使用的数据

    2024年02月03日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包