题解Code+7 教科书般的亵渎

Posted tweetuzki

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了题解Code+7 教科书般的亵渎相关的知识,希望对你有一定的参考价值。

题意:

有一个集合 (S),初始为空。进行 (m) 次操作哟,分下列两种:

  • 修改:向集合内添加一个 ([1, n]) 内的正整数 (h)
  • 定义 (f(d))([1, d], [d + 1, 2d], [2d + 1, 3d], ldots) 这样取,第一次没有在集合内找到对应区间内元素的回数。询问:给出 (l, r),求 (sum limits_{d = l} ^ r f(d))

(n le 10 ^ 5)(m le 10 ^ 6)

比赛时只会这题,自闭。

答案是调和级数 (mathcal{O}(n log n)) 级别的,考虑对每个 (d) 都用个 ( exttt{std::set}) 维护它的所有还没有覆盖集合内元素的区间,那么这个 (d) 对应的答案就是这些区间中最小的那个的编号,(l, r) 对应的答案直接树状数组求个区间和。

修改时,只要保证枚举到的每个区间都是还没有被覆盖的,这样均摊下去复杂度就是对的。难点在如何维护这个东西。

首先,跨越 (h) 的没有被覆盖的区间只和 (h)(S) 中的前驱和后缀有关。分别记为 (pre, nxt)

那么我们要找到的区间,设起点为 (x),长度为 (l)(l mid x)),大概是这样的:(pre le x lt h le x + l lt nxt)(也可以写作 (pre le x - l lt h le x lt nxt))。

我们在 ([pre, h))([h, nxt)) 较短一侧内枚举起点 (x),然后二分找到 (x) 最早跨到另外一个区间的因子,不断删除这样的区间直到跨到区间之外。就完成了只枚举到了所有还未被覆盖的跨越 (h) 的区间,维护直接 ( exttt{std::set}) 上删除,复杂度是 (mathcal{O}(n log^2 n))(也可以用可删堆维护得到常数小一些的 (log^2))。

但这里枚举起点和二分的复杂度又是怎么保证的呢?这其实就是一个把大区间 ([pre, nxt)) 分成 ([pre, h))([h, nxt)) 的过程,即启发式分裂,是 (mathcal{O}(n log n)) 的,再加上一个二分就是 (mathcal{O}(n log^2 n))

每次查询时只要到树状数组里查,复杂度 (mathcal{O}(m log n))

故总时间复杂度 (mathcal{O}(n log^2 n + m log n))

代码:

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <set>
#include <vector>

const int MaxN = 100000;

int N, M;
std::set<int> A;
std::set<int> S[MaxN + 5];
std::vector<int> Fac[MaxN + 5];

struct FenwickTree {
  long long t[MaxN + 5];
  inline int lowbit(int i) { return i & -i; }
  inline void update(int x, long long v) { for (int i = x; i <= N; i += lowbit(i)) t[i] += v; }
  inline long long query(int x) { long long res = 0; for (int i = x; i > 0; i -= lowbit(i)) res += t[i]; return res; }
  inline long long qrange(int l, int r) { return query(r) - query(l - 1); }
};
FenwickTree T;

void solve() {
  for (int i = 1; i <= N; ++i)
    for (int j = 1; (j - 1) * i <= N; ++j)
      S[i].insert(j);
  for (int i = 1; i <= N; ++i) {
    for (int j = 0; j * i <= N; ++j)
      Fac[i * j].push_back(i);
    S[i].insert(*(--S[i].end()) + 1);
  }
  for (int i = 1; i <= N; ++i)
    T.update(i, *S[i].begin());
  A.insert(0), A.insert(2 * N);
  for (int q = 1; q <= M; ++q) {
    int opt;
    scanf("%d", &opt);
    if (opt == 1) {
      int x;
      scanf("%d", &x);
      if (A.count(x) != 0) continue;
      std::set<int>::iterator iter_nxt = A.lower_bound(x), iter_pre = iter_nxt;
      iter_pre--;
      int pre = *iter_pre, nxt = *iter_nxt;
      A.insert(x);
      if (x - pre < nxt - x) {
        for (int i = pre; i < x; ++i) {
          std::vector<int>::iterator it = std::lower_bound(Fac[i].begin(), Fac[i].end(), x - i);
          while (it != Fac[i].end()) {
            int d = *it;
            if (i + d >= nxt) break;
            int blocknum = (x - 1) / d + 1;
            T.update(d, -*S[d].begin());
            S[d].erase(blocknum);
            T.update(d, *S[d].begin());
            it++;
          }
        }
      } else {
        for (int i = x; i < nxt; ++i) {
          std::vector<int>::iterator it = std::upper_bound(Fac[i].begin(), Fac[i].end(), i - x);
          while (it != Fac[i].end()) {
            int d = *it;
            if (i - d < pre) break;
            int blocknum = (x - 1) / d + 1;
            T.update(d, -*S[d].begin());
            S[d].erase(blocknum);
            T.update(d, *S[d].begin());
            it++;
          }
        }
      }
    } else {
      int l, r;
      scanf("%d %d", &l, &r);
      printf("%lld
", T.qrange(l, r));
    }
  }
}

int main() {
  scanf("%d %d", &N, &M);
  solve();
  return 0;
}

以上是关于题解Code+7 教科书般的亵渎的主要内容,如果未能解决你的问题,请参考以下文章

洛谷P4593 [TJOI2018]教科书般的亵渎 数学

教科书般的亵渎

教科书般的亵渎

P4593 [TJOI2018]教科书般的亵渎(拉格朗日插值)

计蒜客 教科书般的亵渎

luogu P4593 [TJOI2018]教科书般的亵渎