• AcWing算法进阶课 莫队


     基础莫队

    2492. HH的项链

    题目:

    HH 有一串由各种漂亮的贝壳组成的项链。

    HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含义。

    HH 不断地收集新的贝壳,因此他的项链变得越来越长。

    有一天,他突然提出了一个问题:某一段贝壳中,包含了多少种不同的贝壳?

    这个问题很难回答,因为项链实在是太长了。

    于是,他只好求助睿智的你,来解决这个问题。

    输入格式

    第一行:一个整数 NN,表示项链的长度。

    第二行:NN 个整数,表示依次表示项链中贝壳的编号(编号为 00 到 10000001000000 之间的整数)。

    第三行:一个整数 MM,表示 HH 询问的个数。

    接下来 MM 行:每行两个整数,LL 和 RR,表示询问的区间。

    输出格式

    MM 行,每行一个整数,依次表示询问对应的答案。

    数据范围

    1N500001≤N≤50000,
    1M2×1051≤M≤2×105,
    1LRN1≤L≤R≤N

    输入样例:

    6
    1 2 3 4 3 5
    3
    1 2
    3 5
    2 6
    

    输出样例:

    2
    2
    4

    代码:

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 50010, M = 200010, S = 1000010;
    int n, m, T;
    int w[N], ans[M];
    struct Q{
      int l, r, i, x, y;
      inline Q() {}
      inline Q(int l, int r, int i) : l(l), r(r), i(i) {x = l / T, y = r;}
      inline friend bool operator< (const Q &a, const Q &b)
      {
          return a.x == b.x ? (a.x & 1) ? a.y < b.y : a.y > b.y : a.x < b.x;
      }
    }q[M];
    int cnt[S];
    
    void add(int x, int& res)
    {
      //第一次出现,说明出现了新的贝壳,标记+1
      if (!cnt[x]) res ++;
      cnt[x] ++;
    }
    
    void del(int x, int& res)
    {
      //将当前-1,如果-1为0,则贝壳种类-1
      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);
      T = sqrt((double) n * n / m);
    
      for (int i = 0; i < m; i ++ )
      {
        int l, r; scanf("%d%d", &l, &r);
        q[i] = {l, r, i};
      }
      sort(q, q + m);
    //   for (int i = 0; i < m; i ++ ) 
    //     printf("[l = %d, r = %d]
    ", q[i].l, q[i].r);
      for (int k = 0, l = 1, r = 0, res = 0; k < m; k ++ )
      {
        while (l < q[k].l) del(w[l ++ ], res);
        while (l > q[k].l) add(w[-- l ], res);
        while (r < q[k].r) add(w[++ r ], res);
        while (r > q[k].r) del(w[r -- ], res);
        ans[q[k].i] = res; 
      }
      for (int i = 0; i < m; i ++ ) printf("%d
    ", ans[i]);
      
      return 0;
    }
    

      

    待修莫队

    2521. 数颜色

    题目:

    墨墨购买了一套 NN 支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。

    墨墨会像你发布如下指令:

    1. Q L R 代表询问你从第 LL 支画笔到第 RR 支画笔中共有几种不同颜色的画笔。
    2. R P Col 把第 PP 支画笔替换为颜色 ColCol。

    为了满足墨墨的要求,你知道你需要干什么了吗?

    输入格式

    第 11 行两个整数 N,MN,M,分别代表初始画笔的数量以及墨墨会做的事情的个数。

    第 22 行 NN 个整数,分别代表初始画笔排中第 ii 支画笔的颜色。

    第 33 行到第 2+M2+M 行,每行分别代表墨墨会做的一件事情,格式见题干部分。

    输出格式

    对于每一个 Query 的询问,你需要在对应的行中给出一个数字,代表第 LL 支画笔到第 RR 支画笔中共有几种不同颜色的画笔。

    数据范围

    1N,M100001≤N,M≤10000,
    修改操作不多于 10001000 次,
    所有的输入数据中出现的所有整数均大于等于 11 且不超过 106106。

    输入样例:

    6 5
    1 2 3 4 5 5
    Q 1 4
    Q 2 6
    R 1 2
    Q 1 4
    Q 2 6
    

    输出样例:

    4
    4
    3
    4

    分析:

    代码:

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 10010, S = 1000010;
    int n, m, mq, mc, len;
    int w[N], cnt[S], ans[N];
    struct Query{
      int id, l, r, t;
    }q[N];
    struct Modify{
      int p, c; 
    }c[N];
    int get(int x)
    {
      return x / len;
    }
    bool cmp(const Query& a, const Query& b)
    {
      int al = get(a.l), ar = get(a.r);
      int bl = get(b.l), 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[3];
        int a, b; 
        scanf("%s%d%d", op, &a, &b);
        if (*op == 'Q')
        {
          //多加一维,t,表示在查询区间[L,R]的时候,已经修改了t次,可以将t理解为时间范围[1,m]
          q[++ mq] = {mq, a, b, mc};
        }
        else 
        { 
          c[++ mc] = {a, b};
        }
      }
    
      len = cbrt((double)n * mc) + 1;
      sort(q + 1, q + 1 + mq, cmp);
      for (int i = 1, l = 1, r = 0, t = 0, res = 0; i <= mq; i ++ )
      {
        while (l < q[i].l) del(w[l ++ ], res);
        while (l > q[i].l) add(w[-- l ], res);
        while (r < q[i].r) add(w[++ r ], res);
        while (r > q[i].r) del(w[r -- ], res);
        while (t < q[i].t)
        {
          t ++;
          if (c[t].p >= l && c[t].p <= r) 
          {
            del(w[c[t].p], res);
            add(c[t].c, res);
          }
          swap(w[c[t].p], c[t].c);
        }
        while (t > q[i].t)
        {
          if (c[t].p >= l && c[t].p <= r)
          {
            del(w[c[t].p], res);
            add(c[t].c, res);
          }
          swap(w[c[t].p], c[t].c);
          t --;      
        }
        ans[q[i].id] = res;
      }
      for (int i = 1; i <= mq; i ++ ) printf("%d
    ", ans[i]);
      return 0;
    }
    

      

    回滚莫队

    2523. 历史研究

    题目:

    IOI 国历史研究的第一人——JOI 教授,最近获得了一份被认为是古代 IOI 国的住民写下的日记。

    JOI 教授为了通过这份日记来研究古代 IOI 国的生活,开始着手调查日记中记载的事件。

    日记中记录了连续 NN 天发生的时间,大约每天发生一件。

    事件有种类之分。第 ii 天 (1iN)(1≤i≤N) 发生的事件的种类用一个整数 XiXi 表示,XiXi 越大,事件的规模就越大。

    JOI 教授决定用如下的方法分析这些日记:

    1. 选择日记中连续的一些天作为分析的时间段
    2. 事件种类 tt 的重要度为 t×t× (这段时间内重要度为 tt 的事件数)
    3. 计算出所有事件种类的重要度,输出其中的最大值

    现在你被要求制作一个帮助教授分析的程序,每次给出分析的区间,你需要输出重要度的最大值。

    输入格式

    第一行两个空格分隔的整数 NN 和 QQ,表示日记一共记录了 NN 天,询问有 QQ 次。

    接下来一行 NN 个空格分隔的整数 X1XNX1…XN,XiXi 表示第 ii 天发生的事件的种类。

    接下来 QQ 行,第 ii 行 (1iQ)(1≤i≤Q) 有两个空格分隔整数 AiAi 和 BiBi,表示第 ii 次询问的区间为 [Ai,Bi][Ai,Bi]。

    输出格式

    输出 QQ 行,第 ii 行 (1iQ)(1≤i≤Q) 一个整数,表示第 ii 次询问的最大重要度。

    数据范围

    1N1051≤N≤105,
    1Q1051≤Q≤105,
    1Xi1091≤Xi≤109

    输入样例:

    5 5
    9 8 7 8 9
    1 2
    3 4
    4 4
    1 4
    2 4
    

    输出样例:

    9
    8
    8
    16
    16

    代码:

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    const int N = 100010;
    int n, m, T;
    int a[N], cnt[N];
    LL ans[N];
    struct Query{
      int id, l, r;
    }q[N];
    vector<int> nums;
    int get(int x)
    {
      return x / T;
    }
    bool cmp(const Query& a, const Query& b)
    {
      int x = get(a.l), y = get(b.l);
      if (x != y) return x < y;
      return a.r < b.r;
    }
    void add(int x, LL& ans)
    {
      if (!cnt[x]) ans ++;
      cnt[x] ++;
    }
    int main()
    {
      scanf("%d%d", &n, &m);
      for (int i = 1; i <= n; i ++ ) 
      {
        scanf("%d", &a[i]);
        nums.push_back(a[i]);
      }
      sort(nums.begin(), nums.end());
      nums.erase(unique(nums.begin(), nums.end()), nums.end());
      for (int i = 1; i <= n; i ++ ) 
        a[i] = lower_bound(nums.begin(), nums.end(), a[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);
      for (int x; x < m;)
      {
        int y = x;
        //将左区间在同一块内的所有区间找到
        while (y < m && get(q[x].l) == get(q[y].l)) y ++;
        //找到当前块的区间右端点
        int right = get(q[x].l) * T + T - 1;
        //将所有在同一块内的所有询问暴力求解
        while (x < y && q[x].r <= right)
        {
          LL res = 0;
          for (int i = q[x].l; i <= q[x].r; i ++ ) add(a[i], res);
          ans[q[x].id] = res;
          //这里的每个询问的区间左端点是按照分块排序的,所以,在分块内,不具有递增性
          //所以,我们不可能只进行add操作就可以将区间求解出来,而是需要不断地反复求取
          //所以,在求解的时候,需要add,再清空,再add
          for (int i = q[x].l; i <= q[x].r; i ++ ) cnt[a[i]] --;
          x ++;
        }
        //求块间的询问,询问的区间左端点在同一块中,右端点在其他块中,按递增顺序
        LL res = 0;
        int l = right + 1, r = right;
        while (x < y)
        {
          //将r扩展到当前询问的区间右端点,在后面的块内
          while (r < q[x].r) add(a[++ r], res);
          //res在后面的分块内是不变的,但是会被l所在分块修改,所以,先备份后面分块的res
          LL backup = res;
          //将l扩展到当前询问的区间左端点,在l所在的块内
          while (l > q[x].l) add(a[-- l], res);
          ans[q[x].id] = res;
          //将l所在块内造成的影响清除掉[保证了只会删到第一个区间的right]
          while (l < right + 1) cnt[a[x ++ ]] --;
          //恢复后面分块的res,消除l所在分块的影响
          res = backup;
          x ++;
        }
        //每移动到新的分块后,我们的区间左端点的add要重算,不如直接清空了
        memset(cnt, 0, sizeof cnt);
      }
      for (int i = 0; i < m; i ++ ) printf("%lld
    ", ans[i]);
      return 0;
    }
    

      

    树上莫队

    2534. 树上计数

    题目:

    给定一棵 NN 个节点的树,节点编号从 11 到 NN,每个节点都有一个整数权值。

    现在,我们要进行 MM 次询问,格式为 u v,对于每个询问你需要回答从 uu 到 vv 的路径上(包括两端点)共有多少种不同的点权值。

    输入格式

    第一行包含两个整数 N,MN,M。

    第二行包含 NN 个整数,其中第 ii 个整数表示点 ii 的权值。

    接下来 N1N−1 行,每行包含两个整数 x,yx,y,表示点 xx 和点 yy 之间存在一条边。

    最后 MM 行,每行包含两个整数 u,vu,v,表示一个询问。

    输出格式

    共 MM 行,每行输出一个询问的答案。

    数据范围

    1N400001≤N≤40000,
    1M1051≤M≤105,
    1x,y,u,vN1≤x,y,u,v≤N,
    各点权值均在 intint 范围内。

    输入样例:

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

    输出样例:

    4
    4

    分析:

    树上的莫队,通过DFS序,或者说欧拉序列将树上的节点,映射成下标

    代码:

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 100005;
    int n, m, len;
    int h[N], e[N], ne[N], idx;
    int w[N];
    vector<int> nums;
    int fa[N][17], depth[N];
    int seq[N], top, first[N], last[N];
    int cnt[N], st[N], ans[N];
    struct Query{
      int id, l, r, p;
    }q[N];
    
    void add_edges(int a, int b)
    {
      e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
    }
    void dfs(int x, int father)
    {
      seq[++ top] = x;
      first[x] = top;
      for (int i = h[x]; ~i; i = ne[i])
      {
        int y = e[i];
        if (y == father) continue;
        dfs(y, x);
      }
      seq[ ++ top] = x;
      last[x] = top;
    }
    void bfs()
    {
      memset(depth, 0x3f, sizeof depth);
      depth[0] = 0, depth[1] = 1;
      queue<int> q;
      q.push(1);
      while (!q.empty())
      {
        int x = q.front();
        q.pop();
        for (int i = h[x]; ~i; i = ne[i])
        {
          int y = e[i];
          if (depth[y] > depth[x] + 1)
          {
            depth[y] = depth[x] + 1;
            q.push(y);
            fa[y][0] = x;
            for (int k = 1; k <= 16; k ++ )
              fa[y][k] = fa[fa[y][k - 1]][k - 1];
          }
        }
      }
    }
    int LCA(int x, int y)
    {
      if (depth[x] < depth[y]) swap(x, y);
      for (int k = 16; k >= 0; k -- )
        if (depth[fa[x][k]] >= depth[y])
          x = fa[x][k];
      if (x == y) return y;
      for (int k = 15; k >= 0; k -- )
        if (fa[x][k] != fa[y][k])
        {
          x = fa[x][k];
          y = fa[y][k];
        }
      return fa[x][0];
    }
    int get(int x)
    {
      return x / len;
    }
    bool cmp(const Query& a, Query& b)
    {
      int x = get(a.l), y = get(b.l);
      if (x != y) return x < y;
      return a.r < b.r;
    }
    void add(int x, int& res)
    {
      //注意这里的x是传入的树的节点,也就是我们映射后的数组下标,并不是元素值
      st[x] ^= 1;
      if (st[x] == 0)
      {
        cnt[w[x]] --;
        if (!cnt[w[x]]) res --;
      }
      else
      {
        if (!cnt[w[x]]) res ++;
        cnt[w[x]] ++;
      }
    }
    int main()
    {
      memset(h, -1, sizeof h);
      scanf("%d%d", &n, &m);
      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 x, y; scanf("%d%d", &x, &y);
        add_edges(x, y); add_edges(y, x);
      }
      dfs(1, -1);
      bfs();
      for (int i = 0; i < m; i ++ )
      {
        int x, y; scanf("%d%d", &x, &y);
        if (first[x] > first[y]) swap(x, y);
        int father = LCA(x, y);
        if (father == x) q[i] = {i, first[x], first[y]};
        else q[i] = {i, last[x], first[y], father};
      }
      len = sqrt(top);
      sort(q, q + m, cmp);
      for (int i = 0, l = 1, r = 0, res = 0; i < m; i ++ )
      {
        while (l < q[i].l) add(seq[l ++ ], res);
        while (l > q[i].l) add(seq[-- l ], res);
        while (r < q[i].r) add(seq[++ r ], res);
        while (r > q[i].r) add(seq[r -- ], res);
        if (q[i].p) add(q[i].p, res);
        ans[q[i].id] = res;
        if (q[i].p) add(q[i].p, res);
       }
      for (int i = 0; i < m; i ++ ) printf("%d
    ", ans[i]);
      return 0;
    }
    

      

  • 相关阅读:
    oracle pl/sql 中目录的创建
    oracle pl/sql中创建视图
    oracle pl/sql 函数中链表的使用
    oracle pl/sql 中的触发器
    (转载)gcc/g++打印头文件包含顺序和有效性
    (转载)Linux平台可以用gdb进行反汇编和调试
    (转载)轻量级Web服务器Lighttpd的编译及配置(for x86linux)
    (转载)浮点数的二进制表示
    gdb如何进行清屏
    gdb设置运行参数
  • 原文地址:https://www.cnblogs.com/Iamcookieandyou/p/15053835.html
Copyright © 2020-2023  润新知