ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

P4168 [Violet]蒲公英

2021-01-14 15:02:18  阅读:236  来源: 互联网

标签:Tmp NmR int 复杂度 众数 Rg Violet 蒲公英 P4168


题意

Luogu P4168

给 \(n\) 个数, 询问区间众数, 询问 \(m\) 次.

\(1 \leq n \leq 40000\), \(1 \leq m \leq 50000\), \(1 \leq a_i \leq 10^9\)

分析

区间查询一般会想到线段树, 但是线段树不能维护众数这种不能合并的信息.

朴素

\(l\) 到 \(r\) 扫一遍, 统计数字出现数量, 然后扫一遍计数器.

\(O(mna_{max})\), 约为 \(2 * 10^{27}\)

离散化

考虑离散化, 最多有 \(n\) 个不同的数, 预处理复杂度 \(O(nlogn)\).

优化成 $O(mn^2) = \(8 * 10^{13}\)

前缀和

用一个二维数组 \(Ap[i][j]\) 记录前 \(i\) 个数中, \(j\) 出现几次.

空间复杂度 \(O(mn) = 2 * 10^9\). 预处理复杂度 \(O(n)\), 时间复杂度 \(O(mn)\).

分块

整体维护, 局部朴素

将 \(n\) 个数分成长度为 \(Rg\) 的 \(NmR\) 块, 然后以块为单位存前缀和, 同时预处理出 \(f[i][j]\) 表示从第 \(i\) 块到第 \(j\) 块 (闭区间) 的众数.

在查询时, 对于区间两端不足一块的, 朴素计算出现在这两区域内存在的数字的出现次数, 然后根据前缀和算出整个区间内这些数字的出现次数, 然后比较其中出现最多的数字出现次数和掐头去尾后整块的众数的出现次数, 因为区间众数不是在两头出现过, 就是整块区的众数.

这种做法, \(Ap\) 和 \(f\) 数组预处理复杂度 \(O(nNmR + NmR^2Rg)\), 总复杂度优化到了 \(O(mRg + nNmR + NmR^2Rg)\).

为了使复杂度最小, 选取 \(NmR\) 时使得 \(NmR \approx Rg \approx \sqrt{n} \approx \sqrt{m}\), 由于本题 \(m\), \(n\) 较接近, 所以姑且取 \(\sqrt{n}\).

实现

详见注释, 快读 RD(), 头文件, 变量定义等略.

int main() {
  n = RD();
  m = RD();
  memset(Ap, 0, sizeof(Ap));
  Rg = max(int(sqrt(n)), 1);  //确定Rg
  NmR = (n + Rg - 1) / Rg;    //推得NmR
  for (register int i(1); i <= n; ++i) {
    a[i] = RD();
    b[i] = a[i];  //创建 a[]副本
  }
  sort(b + 1, b + n + 1);
  for (register int i(1); i <= n; ++i) {
    if (b[i] != b[i - 1]) {
      Ar[++Cnta] = b[i];  // Ar 存严格次序中第 k 小的数
    }
  }
  for (register int i(1); i <= n; ++i) {  //离散化
    a[i] = lower_bound(Ar + 1, Ar + Cnta + 1, a[i]) -
           Ar;  //将每个a[i]变成小于等于n的数
  }
  for (register int i(1); i < NmR; ++i) {  //处理Ap[][]
    for (register int j(Rg * (i - 1) + 1); j <= Rg * i; ++j) {
      ++Ap[i][a[j]];
    }
    for (register int j(1); j <= Cnta; ++j) {  //继承给下一块
      Ap[i + 1][j] = Ap[i][j];
    }
  }
  //最后一行
  for (register int i(Rg * (NmR - 1) + 1); i <= n; ++i) {  //最后一行特殊处理
    ++Ap[NmR][a[i]];
  }
  for (register int i(1); i < NmR; ++i) {  //处理长度为 1 块的区间的 f[][]
    Tmp = 0;
    for (register int j(Rg * (i - 1) + 1); j <= Rg * i;
         ++j) {  //枚举每一个出现过的数字
      if (Ap[i][Tmp] - Ap[i - 1][Tmp] <= Ap[i][a[j]] - Ap[i - 1][a[j]]) {
        if (Ap[i][Tmp] - Ap[i - 1][Tmp] == Ap[i][a[j]] - Ap[i - 1][a[j]]) {
          if (Tmp > a[j]) {
            Tmp = a[j];
          }
        } else {
          Tmp = a[j];
        }
      }
    }
    f[i][i] = Tmp;
  }
  Tmp = 0;
  for (register int i(Rg * (NmR - 1) + 1); i <= n; ++i) {  //最后一行特殊处理
    if (Ap[NmR][Tmp] - Ap[NmR - 1][Tmp] <= Ap[NmR][a[i]] - Ap[NmR - 1][a[i]]) {
      if (Ap[NmR][Tmp] - Ap[NmR - 1][Tmp] ==
          Ap[NmR][a[i]] - Ap[NmR - 1][a[i]]) {
        if (Tmp > a[i]) {
          Tmp = a[i];
        }
      } else {
        Tmp = a[i];
      }
    }
    f[NmR][NmR] = Tmp;
  }
  for (register int i(1); i <= NmR; ++i) {
    for (register int j(i + 1); j <= NmR; ++j) {  //处理全部f[][]
      if (f[i][j - 1] == f[j][j]) {               //共同众数无需处理
        f[i][j] = f[j][j];
      } else {
        Tmp = f[i][j - 1];
        for (register int k(Rg * (j - 1) + 1); k <= min(Rg * j, n);
             ++k) {  //枚举出现过的数字
          if (Ap[j][Tmp] - Ap[i - 1][Tmp] <= Ap[j][a[k]] - Ap[i - 1][a[k]]) {
            if (Ap[j][Tmp] - Ap[i - 1][Tmp] == Ap[j][a[k]] - Ap[i - 1][a[k]]) {
              if (Tmp > a[k]) {  //数字小的优先
                Tmp = a[k];
              }
            } else {
              Tmp = a[k];  //更新众数
            }
          }
        }
        f[i][j] = Tmp;  //众数以确定
      }
    }
  }
  for (register int i(1); i <= m; ++i) {  //处理询问
    L = (RD() + Lst - 1) % n + 1;
    R = (RD() + Lst - 1) % n + 1;  //区间生成(强制在线)
    if (L > R) {                   //判左大右小
      swap(L, R);
    }
    Lr = (L + Rg - 1) / Rg + 1;
    Rr = R / Rg;    //处理包含的最左块和最右块
    if (Lr > Rr) {  //整块不存在
      for (register int j(L); j <= R; ++j) {  //直接朴素
        Tmpp[a[j]] = 0;                       //清空计数器(下同)
      }
      for (register int j(L); j <= R; ++j) {
        ++Tmpp[a[j]];
      }
      Tmp = 0;
      for (register int j(L); j <= R; ++j) {
        if (Tmpp[Tmp] <= Tmpp[a[j]]) {
          if (Tmpp[Tmp] == Tmpp[a[j]]) {
            if (Tmp > a[j]) {
              Tmp = a[j];
            }
          } else {
            Tmp = a[j];
          }
        }
      }
      Lst = Tmp;
    } else {            //有整块
      Tmp = f[Lr][Rr];  //先和判整块众数出现次数比较
      Tmpp[Tmp] = 0;    //别忘了这里
      for (register int j(L); j <= Rg * (Lr - 1); ++j) {  //掐头
        Tmpp[a[j]] = 0;
      }
      for (register int j(Rg * Rr + 1); j <= R; ++j) {  //去尾
        Tmpp[a[j]] = 0;
      }
      for (register int j(L); j <= Rg * (Lr - 1); ++j) {
        ++Tmpp[a[j]];
      }
      for (register int j(Rg * Rr + 1); j <= R; ++j) {
        ++Tmpp[a[j]];
      }
      for (register int j(L); j <= Rg * (Lr - 1); ++j) {  //开始迭代
        if (Tmpp[Tmp] + Ap[Rr][Tmp] - Ap[Lr - 1][Tmp] <=
            Tmpp[a[j]] + Ap[Rr][a[j]] -
                Ap[Lr - 1][a[j]]) {  //当前数字出现次数和当前已知众数出现次数
          if (Tmpp[Tmp] + Ap[Rr][Tmp] - Ap[Lr - 1][Tmp] ==
              Tmpp[a[j]] + Ap[Rr][a[j]] - Ap[Lr - 1][a[j]]) {
            if (Tmp > a[j]) {
              Tmp = a[j];
            }
          } else {
            Tmp = a[j];
          }
        }
      }
      for (register int j(Rg * Rr + 1); j <= R; ++j) {  //尾操作同头
        if (Tmpp[Tmp] + Ap[Rr][Tmp] - Ap[Lr - 1][Tmp] <=
            Tmpp[a[j]] + Ap[Rr][a[j]] - Ap[Lr - 1][a[j]]) {
          if (Tmpp[Tmp] + Ap[Rr][Tmp] - Ap[Lr - 1][Tmp] ==
              Tmpp[a[j]] + Ap[Rr][a[j]] - Ap[Lr - 1][a[j]]) {
            if (Tmp > a[j]) {
              Tmp = a[j];
            }
          } else {
            Tmp = a[j];
          }
        }
      }
      Lst = Tmp;
    }
    Lst = Ar[Lst];  //离散化后的值转化为原始值
    printf("%d\n", Lst);
  }
  return 0;
}

标签:Tmp,NmR,int,复杂度,众数,Rg,Violet,蒲公英,P4168
来源: https://www.cnblogs.com/Wild-Donkey/p/14277240.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有