P4168 [Violet]蒲公英

题意

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;
}
上一篇:SDOI2013 直径(树的直径必经边)


下一篇:「ZJOI2011」最小割