P6272 [湖北省队互测2014] 没有人的算术 题解

cnblog

本文参考了 湖北省队互测 Week1 解题报告,在部分之处说明可能不如原题解,如有错误请指出。

洛谷上的题面缺失了特殊性质,不过原题的特殊性质还是比较具有启发性的,下面是原题面中的数据范围。

测试点 \(1\)

考察选手的读题能力。按照题目提供的比较方式暴力递归即可。

测试点 \(2 \sim 3\)

考虑特殊性质随机生成的实质。注意到如果我们一直操作 C 1 1 1 的话,比较树的大小是 \(O(2^n)\) 的。但是在随机数据下,一个数字并不会被合并很多次,所以可以忽略比较的常数,直接用线段树维护序列,时间复杂度为 \(O(m \log n)\)

测试点 \(4 \sim 5\)

注意到暴力维护的瓶颈实质上在于比较两个数的复杂度,例如在上个测试点中我们就是依赖 随机数据下两个数的比较复杂度低 来暴力解决的,那么如果规定了总共出现过的不同的数字不会超过 \(1000\) 个,那么不妨采用记忆化的思想,设 \(cmp_{i, j}\) 表示第 \(i\) 个出现的数字和第 \(j\) 个出现的数字比较的结果,暴力把这张 \(V^2\) 的表打出来,就能把比较的复杂度降为 \(O(1)\)

依然采用线段树实现,复杂度为 \(O(V^2 + m\log n)\)

测试点 \(6 \sim 7\)

仔细想想上面的做法在某种程度上没什么必要,如果我们能从小到大直接维护出数字的相对顺序,也能直接比较两个数。
考虑用平衡树维护这个序列(大小关系的序列),一件很好的事情是,我们的两个关键字都是已经出现过的值,那么只要根据 \(x_L, x_R\) 的排名就能在 \(O(\log V)\) 的时间内比较一对数。

这样比较一次是 \(O(\log V)\),那么插入一个数就是 \(O(\log^2 V)\) 的(插入路径上的所有点都需要查排名)。查询也就变成 \(O(\log n \log V)\) 的,那么复杂度为 \(O(m(\log^2 V + \log n\log V))\)

测试点 \(8 \sim 10\)

上面所有的铺垫都在将答案指向一个地方——离散化。例如 \(50\) 分时,我们的离散化工具是 \(cmp_{i,j}\);在 \(70\) 分时,我们的离散化工具是两个数的排名。

有没有什么能够 \(O(1)\) 实现两个数比较的离散化方法呢?如果我们使用 精度足够 的实数,似乎也能实现这一想法!

于是我们直接考虑在 BST 上从根开始给每个节点赋值,例如从根开始是 \([0, V]\),然后分裂为 \([0, \dfrac{V}{2}),(\dfrac{V}{2}, V]\),以此类推。问题到这里还没结束,注意我们上文提到了如果想用实数进行比较,一定要保证 精度,于是我们想起平衡树来。

平衡树的树高是 \(\log n\) 级别,那么最底层对精度的要求为 \(2^{-\log n} = \dfrac{1}{n}\) ,而 \(n\) 只有 \(10^5\) 而已,所以精度是完全够用的。

但是,注意平衡树用于维护平衡的操作——旋转。麻烦的是,在进行一次旋转后,我们子树内所有点的值域都被完全打乱了,这个时候再插入数时每个点的值域就完全不一样了,所以考虑用结构稳定,依赖定期重构维护平衡结构的重量平衡树,这样我们可以在每次重构的过程中顺便计算每个点维护的值域。

大功告成,复杂度即为 \(O(m(\log V + \log n))\)。实现用了替罪羊树。

// 如果命运对你缄默, 那就活给他看。
// #pragma GCC optimize(1)
// #pragma GCC optimize(2)
// #pragma GCC optimize(3)
// #pragma GCC optimize("Ofast", "inline", "-ffast-math")
// #pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include <bits/stdc++.h>
using namespace std;
typedef long long LL; 
// #define int LL
const int maxn = 600010;
int w[maxn], n, m;
namespace scapegt {
  int tot, rt;
  struct Node {
    int x, y;
    double v;
    int ls, rs;
    int c, sz;
  } t[maxn];
  const double ap = 0.75;
  inline void pu(int u) {
    t[u].sz = t[t[u].ls].sz + t[t[u].rs].sz + 1;
  } 
  inline bool balance(int u) {
    double lw = t[u].sz * ap;
    return max(t[t[u].ls].sz, t[t[u].rs].sz) <= lw;
  }
  int acc = 0;
  int v[maxn];
  inline void dfs1(int u) {
    if(!u) return ;
    dfs1(t[u].ls);
    v[++ acc] = u;
    dfs1(t[u].rs);
  }
  inline int rebuild(int l, int r, double vl, double vr) {
    if(l > r) return 0; 
    int mid = l + r >> 1;
    double vmid = (vl + vr) / 2;
    int u = v[mid];
    t[u].v = vmid;
    t[u].ls = rebuild(l, mid - 1, vl, vmid);
    t[u].rs = rebuild(mid + 1, r, vmid, vr);
    pu(u);
    return u;
  }
  inline void dfs(int& u, double vl, double vr) {
    acc = 0;
    dfs1(u);
    u = rebuild(1, acc, vl, vr);
  }
  inline void insert(int& u, double vl, double vr, int l, int r, int k) {
    double mid = (vl + vr) / 2;
    if(!u) {
      u = ++ tot;
      t[u].x = w[l], t[u].y = w[r];
      t[u].v = mid;
      t[u].c = t[u].sz = 1;
      w[k] = u;
      return ;
    }
    if(t[t[u].x].v == t[w[l]].v && t[t[u].y].v == t[w[r]].v) {
      w[k] = u;
      t[u].c ++, pu(u);
      return ;
    }
    if(t[w[l]].v < t[t[u].x].v || (t[w[l]].v == t[t[u].x].v && t[w[r]].v < t[t[u].y].v)) insert(t[u].ls, vl, mid, l, r, k);
    else insert(t[u].rs, mid, vr, l, r, k);
    pu(u);
    if(!balance(u)) dfs(u, vl, vr);
  } 
  inline void print(int u) {
    if(!u) return ;
    print(t[u].ls);
    cout << fixed << setprecision(3) << u << ' ' << t[u].v << ' ' << t[u].x << ' ' << t[u].y << " " << '\n';    
    print(t[u].rs);
  }
}
namespace sgt {
  int mx[maxn];
  inline int cmx(int x, int y) {
    return scapegt :: t[w[x]].v >= scapegt :: t[w[y]].v ? x : y; 
  }
  inline void pu(int u) {
    mx[u] = cmx(mx[u << 1], mx[u << 1 | 1]);
  }
  inline void modf(int u, int l, int r, int p) {
    if(l == r) return ;
    int mid = l + r >> 1;
    if(p <= mid) modf(u << 1, l, mid, p);
    else modf(u << 1 | 1, mid + 1, r, p);
    pu(u);
  }
  inline int Q(int u, int l, int r, int ql, int qr) {
    if(ql <= l && r <= qr) return mx[u];
    int mid = l + r >> 1;
    if(qr <= mid) return Q(u << 1, l, mid, ql, qr);
    if(ql > mid) return Q(u << 1 | 1, mid + 1, r, ql, qr);
    return cmx(Q(u << 1, l, mid, ql, qr), Q(u << 1 | 1, mid + 1, r, ql, qr));
  }
  inline void build(int u, int l, int r) {
    mx[u] = l;
    if(l == r) return ;
    int mid = l + r >> 1;
    build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
  }
}
signed main() {
  ios :: sync_with_stdio(false);
  cin.tie(0), cout.tie(0);
  cin >> n >> m;
  char c[2];
  sgt :: build(1, 1, n);
  for(int i = 1; i <= m; ++ i) {
    int l, r, k;
    cin >> c >> l >> r;
    if(*c == 'C') {
      cin >> k;
      scapegt :: insert(scapegt :: rt, 1.0, 1e9, l, r, k);
      sgt :: modf(1, 1, n, k);
    } else {
      cout << sgt :: Q(1, 1, n, l, r) << '\n';
    }
  }
  return 0;
}
posted @   Rainsheep  阅读(100)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示