zoukankan      html  css  js  c++  java
  • [题解] bzoj 3600 没有人的算数 (替罪羊树+线段树)

    - 传送门 -

    http://www.lydsy.com/JudgeOnline/problem.php?id=3600
     

    - 思路 -

    区间操作是线段树无疑,难点在于如何考虑这些数对的大小关系.

    我们考虑一下平衡树,如果在平衡树中每个节点放一个数对,我们规定中序遍历前面的数对大于后面的,那么对于任意一个新数对(x,y)插入时只需知道x,y与每个节点的数对的关系,就可以在log的时间内放入.

    对于x,y与某节点数对的关系,首先要知道x,y一定在平衡树中存在(否则怎么被用来构成新数对?),因此可以log查找x/y与该节点中序遍历的先后关系来确定大小关系.

    但是这样查找log,插入log,总复杂度达到了log^2,完美爆炸,所以我们得考虑在O(1)时间内求出(x,y)与某点对的大小关系.

    然后这里就要用标号的思想了,具体可以参考CLJ的论文
    ,对于平衡树的每一个节点使他们表示一个区间,root表示(0,1),左孩子是(0,mid),右孩子是(mid,1),以此类推,(注意一下精度问题, 虽然这题不卡精度, 但我还是按题解用了(1,long long))节点再记录一下区间中值作为编号,然后我们发现这个编号和中序遍历是一致的,可以在O(1)时间内算出两个数在平衡树中的先后关系.

    注意我们上文中说的x,y都是自己给点对标的号,这些细节就要自己细细地想啦.

    (论文中有讲要用重量平衡树的理由等等...)

    细节见代码.
     

    - 代码 -

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <queue>
    #include <vector>
    #include <ctime>
    #include <cstdlib>
    #define pii pair<int, int>
    #define mp make_pair
    #define ft first
    #define sd second
    #define ls C[rt][0]
    #define rs C[rt][1]
    using namespace std;
    
    template <typename ty> void read(ty &x) {
      x = 0; int f = 1; char ch = getchar();
      while (ch > '9' || ch < '0') { if (ch == '-') f = -1; ch = getchar(); }
      while (ch >= '0' && ch <= '9') { x = x*10 + ch - '0'; ch = getchar(); }
      x *= f;
    }
    template <typename ty> ty Max(ty a, ty b) { return a > b ? a : b; }
    template <typename ty> ty Min(ty a, ty b) { return a < b ? a : b; }
    template <typename ty> int Chkmin(ty &a, ty b) { return a > b ? a = b, 1 : 0; }
    template <typename ty> int Chkmax(ty &a, ty b) { return a < b ? a = b, 1 : 0; }
    
    typedef long long LL;
    typedef double db;
    
    const int inf = 0x7fffffff;
    const int M = 5e5 + 16;
    const int N = 1e5 + 16;
    const db alpha = 0.76;
    const LL MAXN = (1LL << 62) - 1;
    
    //scapegoat_tree
    LL V[M], L[M], R[M];
    int C[M][2], F[M], S[M], A[M], B[M];
    // A,B表示点对的前后两个点在替罪羊树中的编号
    // 注意给0这个(伪)点对的A,B初始化为<1即可,因为点对都是由两个书中的点(序号大于0)构成的
    
    //segment_tree
    int D[N], T[N << 2];
    // D表示数列中的点对应的点对在替罪羊树中的编号
    // T表示线段树中区间的答案(指向一个原数列中的数)
    int CUR[M];
    int root, sz, n, m, l, r, k, tot;
    int ans, num, mpt;
    char CH[10];
    
    bool blc(int rt) {
      return (db) S[rt] * alpha >= (db) S[ls]
        && (db) S[rt] * alpha >= (db) S[rs];
    }
    
    void init() {
      root = sz = 1;
      V[sz] = (1 + MAXN) >> 1;
      L[sz] = 1, R[sz] = MAXN;
      A[sz] = 0, B[sz] = 0;
    }
    
    void recycle(int rt) {
      if (!rt) return;
      recycle(ls);
      CUR[++tot] = rt;
      recycle(rs);
    }
    
    int build(int l, int r, LL x, LL y) {
      if (l > r) return 0;
      int mid = l + r >> 1, now = CUR[mid];
      L[now] = x, R[now] = y, V[now] = (x + y) >> 1;
      F[C[now][0] = build(l, mid - 1, x, V[now])] = now;
      F[C[now][1] = build(mid + 1, r, V[now], y)] = now;
      S[now] = S[C[now][0]] + S[C[now][1]] + 1;
      return now;
    }
    
    void rebuild(int rt) {
      LL l = L[rt], r = R[rt];
      int fa = F[rt], a = (C[fa][1] == rt);
      tot = 0; recycle(rt);
      int tmp = build(1, tot, l, r);
      if (root == rt) root = tmp;
      F[C[fa][a] = tmp] = fa;
    }
    
    int ins(int &rt, int pre, int x, int y) {
      if (!rt) {
        rt = ++ sz; A[sz] = x, B[sz] = y; S[sz] = 1;
        if (C[pre][0] == rt) L[sz] = L[pre], R[sz] = V[pre];
        else L[sz] = V[pre], R[sz] = R[pre];
        V[sz] = L[sz] + R[sz] >> 1; F[rt] = pre;
        return sz;
      }
      if (x == A[rt] && y == B[rt]) return rt;
      int tmp;
      if (V[x] > V[A[rt]] || (x == A[rt] && V[y] > V[B[rt]]))
        tmp = ins(C[rt][1], rt, x, y);
      else
        tmp = ins(C[rt][0], rt, x, y);
      S[rt] = S[ls] + S[rs] + 1;
      if (!blc(rt)) mpt = rt;
      return tmp;
    }
    
    int insert(int x, int y) {
      mpt = 0;
      int ans = ins(root, 0, x, y);
      if (mpt) rebuild(mpt);
      return ans;
    }
    
    void build(int rt, int l, int r) {
      if (l == r) { T[rt] = l; return; }
      int mid = l + r >> 1;
      build(rt << 1, l, mid); build(rt << 1 | 1, mid + 1, r);
      T[rt] = T[rt << 1];
    }
    
    int get_max(int a, int b) {
      int aa = A[D[a]], ab = A[D[b]];
      if (V[aa] < V[ab]) return b;
      if (V[aa] == V[ab] && V[B[D[a]]] < V[B[D[b]]]) return b;
      return a;
    }
    
    void modify(int rt, int l, int r, int p) {
      if (l == r) return;
      int mid = l + r >> 1;
      if (p <= mid) modify(rt << 1, l, mid, p);
      else modify(rt << 1 | 1, mid + 1, r, p);
      T[rt] = get_max(T[rt << 1], T[rt << 1 | 1]);
    }
    
    int query(int rt, int l, int r, int x, int y) {
      if (x <= l && r <= y) return T[rt];
      int mid = l + r >> 1;
      int a = 0, b = 0;
      if (x <= mid) a = query(rt << 1, l, mid, x, y);
      if (y > mid) b = query(rt << 1 | 1, mid + 1, r, x, y);
      if (!a || !b) return a + b;
      else return get_max(a, b);
    }
    
    int main () {
    
      read(n); read(m);
      init();
      for (int i = 1; i <= n; ++ i) D[i] = 1;
      build(1, 1, n);
      while (m --) {
        scanf("%s", CH);
        read(l); read(r);
        if (CH[0] == 'C') {
          read(k);
          D[k] = insert(D[l], D[r]);
          modify(1, 1, n, k);
        }
        else
          printf("%d
    ", query(1, 1, n, l, r));
      }
    
      return 0;
    
    }
    
  • 相关阅读:
    dev DEV控件:gridControl常用属性设置
    C# ListView用法详解
    LeetCode 22_ 括号生成
    LeetCode 198_ 打家劫舍
    LeetCode 46_ 全排列
    LeetCode 121_ 买卖股票的最佳时机
    LeetCode 70_ 爬楼梯
    LeetCode 53_ 最大子序和
    LeetCode 326_ 3的幂
    LeetCode 204_ 计数质数
  • 原文地址:https://www.cnblogs.com/Anding-16/p/8025355.html
Copyright © 2011-2022 走看看