[题解] 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\n", query(1, 1, n, l, r));
  }

  return 0;

}
时间: 2024-08-29 13:23:34

[题解] bzoj 3600 没有人的算数 (替罪羊树+线段树)的相关文章

题解 HDU 3698 Let the light guide us Dp + 线段树优化

http://acm.hdu.edu.cn/showproblem.php?pid=3698 Let the light guide us Time Limit: 5000/2000 MS (Java/Others)    Memory Limit: 62768/32768 K (Java/Others) Total Submission(s): 759    Accepted Submission(s): 253 Problem Description Plain of despair was

「BZOJ3600」没有人的算术 替罪羊树+线段树

题目描述 过长--不想发图也不想发文字,所以就发链接吧-- 没有人的算术 题解 \(orz\)神题一枚 我们考虑如果插入的数不是数对,而是普通的数,这就是一道傻题了--直接线段树一顿乱上就可以了. 于是我们现在只需要解决一个问题--维护这些数的大小关系. 由于这些数具有有序性,我们可以将这些数的值重记为一个数,这样就可以无脑地比较了.并且,由于这些值的大小可能会随着插入而更改,所以要用一棵平衡树来维护. 那么问题来了,这个数取什么值比较好呢? 首先当然可以是排名,不过如果使用排名,每次访问值的时

BZOJ 3022 [Balkan2012]The Best Teams(扫描线+线段树)

[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=3022 [题目大意] 给定n个球员,第i个球员年龄为AGEi,水平为SKILLi. 没有任何两个球员的水平相同.将这些球员按水平排序, 对于一次比赛,你需要选择若干个球员去比赛,但不能同时选择两个水平相邻的球员. m次询问,每次给定a和k,表示要在年龄不超过a的球员中选择不超过k个球员, 请计算skill和的最大值. [题解] 对于询问年龄的限制,我们可以通过扫描线来处理. 我们将所有

Bzoj 4408: [Fjoi 2016]神秘数 可持久化线段树,神题

4408: [Fjoi 2016]神秘数 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 177  Solved: 128[Submit][Status][Discuss] Description 一个可重复数字集合S的神秘数定义为最小的不能被S的子集的和表示的正整数.例如S={1,1,1,4,13}, 1 = 1 2 = 1+1 3 = 1+1+1 4 = 4 5 = 4+1 6 = 4+1+1 7 = 4+1+1+1 8无法表示为集合S的子集的

BZOJ 1112 [POI2008]砖块Klo(可持久化线段树)

[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1112 [题目大意] 给出一个数列,对于一个操作,你可以对一个数+1,或者一个数-1, 问若使得数列中出现长度为m的连续相同的数,最少需要的操作数. [题解] 我们发现对于固定区间求最小操作,等价于求区间中数距离中位数差值和最小, 我们发现区间中位数可以利用主席树求区间kth来实现, 同时在主席树上维护权值线段树的区间真值和,那么对于每个区间中的数, 就能分别维护比中位数小的部分的和以

[BZOJ 1018] [SHOI2008] 堵塞的交通traffic 【线段树维护联通性】

题目链接:BZOJ - 1018 题目分析 这道题就说明了刷题少,比赛就容易跪..SDOI Round1 Day2 T3 就是与这道题类似的..然而我并没有做过这道题.. 这道题是线段树维护联通性的经典模型. 我们线段树的一个节点表示一个区间的联通性,有 6 个 bool 值,表示这个区间的 4 个角上的点之间的联通性. 然后用两个子区间的联通性和两个子区间之间的连边情况合并出整个区间的联通性. 修改某条边时,先在边的数组中修改,然后从这条边所在的点的线段树叶子开始向上 Update . 询问两

[BZOJ 1901] Dynamic Rankings 【树状数组套线段树 || 线段树套线段树】

题目链接:BZOJ - 1901 题目分析 树状数组套线段树或线段树套线段树都可以解决这道题. 第一层是区间,第二层是权值. 空间复杂度和时间复杂度均为 O(n log n). 代码 树状数组套线段树 #include <iostream> #include <cstdlib> #include <cstdio> #include <cmath> #include <algorithm> #include <cstring> usin

bzoj 2733 永无乡 - 并查集 - 线段树

永无乡包含 n 座岛,编号从 1 到 n,每座岛都有自己的独一无二的重要度,按照重要度可 以将这 n 座岛排名,名次用 1 到 n 来表示.某些岛之间由巨大的桥连接,通过桥可以从一个岛 到达另一个岛.如果从岛 a 出发经过若干座(含 0 座)桥可以到达岛 b,则称岛 a 和岛 b 是连 通的.现在有两种操作:B x y 表示在岛 x 与岛 y 之间修建一座新桥.Q x k 表示询问当前与岛 x连通的所有岛中第 k 重要的是哪座岛,即所有与岛 x 连通的岛中重要度排名第 k 小的岛是哪 座,请你输

[BZOJ 3218] A + B Problem 【可持久化线段树 + 网络流】

题目连接:BZOJ - 3218 题目分析 题目要求将 n 个点染成黑色或白色,那么我们可以转化为一个最小割模型. 我们规定一个点 i 最后属于 S 集表示染成黑色,属于 T 集表示染成白色,那么对于每个点 i 就要连边 (S, i, B[i]) 和 (i, T, W[i]). 这样,如果一个点属于 S 集,就要割掉与 T 相连的边,就相当于失去了染成白色的收益. 我们再来考虑 “奇怪的点”,一个点 i 变成奇怪的点的条件是:i 是黑色且存在一个白色点 j 满足 j < i && L