题目描述

过长……不想发图也不想发文字,所以就发链接吧……

没有人的算术

题解

\(orz\)神题一枚

我们考虑如果插入的数不是数对,而是普通的数,这就是一道傻题了——直接线段树一顿乱上就可以了。

于是我们现在只需要解决一个问题——维护这些数的大小关系。

由于这些数具有有序性,我们可以将这些数的值重记为一个数,这样就可以无脑地比较了。并且,由于这些值的大小可能会随着插入而更改,所以要用一棵平衡树来维护。

那么问题来了,这个数取什么值比较好呢?

首先当然可以是排名,不过如果使用排名,每次访问值的时候都要重新在平衡树中查一次,复杂度肯定是\(O(nlog^2n)\)的,基本不现实。

换一个角度可以发现,我们只需要知道大小关系,不需要排名,于是我们可以用实数维护一个数的大小,虽然相邻的数差值大小不同,只要相对大小是正确的就不必担心了……

那么我们可以这样看,在平衡树中每个节点维护一个区间\((l,r)\),表示这棵子树中所有数值都在\((l,r)\)之中,而这棵子树的根的值为\(mid=\frac{(l+r)}{2}\)递归左右子树的时候,将区间分成\((l,mid)\)和\((mid,r)\),完全满足二叉搜索树的性质,并且可以随时在任何位置新增一个数而不影响已有的数的数值。

那么问题就得到了解决,我们用一棵平衡树维护出现过的所有数对,节点上保存这个数对的两个父亲和\((l,r)\),并用\(mid=\frac{(l+r)}{2}\)表示这个节点的数值,然后无脑做插入和询问即可。

但是我们忽略了一个问题,我们应该使用什么样的平衡树?发现那些基于旋转的平衡树在旋转后都会出现一个致命的问题,\((l,r)\)无法维护!因为一次旋转会使得它以及它的子树全部的\((l,r)\)都被改变,复杂度难以承受。于是我们想到了替罪羊树,基于重构的它反正都要全部拍扁了重来,重新为区间赋值不就可以在重构时顺便一起做了吗?

于是,这道题就这么被切掉了……

(内心:哪有说的这么简单)

总结:

替罪羊树的维护方式与\(AVL\)、\(SBT\)、\(Splay\)都不一样,后几种算法都是通过旋转维护平衡,然而替罪羊树却是用重构维护平衡,重构的时候可以重新计算值,不需要通过原来的值进行改变。所以替罪羊树可以维护的信息更加灵活,并且拍扁重建很欢乐常数小。于是替罪羊树非常适合套其他的数据结构……树套树时也要想一想能不能用替罪羊树……

吐槽:植树节到了我们一起来欢乐种树写替罪羊树模板吧!

\(Code:\)

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define N 100005
#define M 800005
#define eps 1e-10
#define inf 1e20
#define equal(a, b) (fabs((a) - (b)) < eps)
#define val(a) (a == -1 ? -inf :(ls[a] + rs[a])/ 2)
#define lim 0.77
char opt[10];
int n, m;
void Insert(int q, int l, int r, int k, int a);
int getint()
{
int p=0;
char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')p=p*10+c-'0',c=getchar();
return p;
}
struct SCT
{
int root, cnt, sz[M];
int num, arr[M];
int lc[M], rc[M];
int lf[M], rf[M];
double ls[M], rs[M];
bool Equal(int u, int l, int r)
{
return equal(val(lf[u]), val(l)) && equal(val(rf[u]), val(r));
}
bool Less(int u, int l, int r)
{
return equal(val(lf[u]), val(l)) ? val(rf[u]) < val(r) : val(lf[u]) < val(l);
}
void Pushup(int q)
{
sz[q] = sz[lc[q]] + sz[rc[q]] + 1;
}
void Getarray(int q)
{
if (lc[q])
Getarray(lc[q]);
arr[++num] = q;
if (rc[q])
Getarray(rc[q]);
}
void Rebuild(int &q, int l, int r, double L, double R)
{
if (l > r)
{
q = 0;
return;
}
int mid = (l + r) >> 1;
double Mid = (L + R) / 2;
q = arr[mid];
ls[q] = L;
rs[q] = R;
Rebuild(lc[q], l, mid - 1, L, Mid);
Rebuild(rc[q], mid + 1, r, Mid, R);
Pushup(q);
}
void Maintain(int &q)
{
num = 0;
Getarray(q);
Rebuild(q, 1, num, ls[q], rs[q]);
}
void Add(int &q, int l, int r, double L, double R, int k)
{
if (!q)
{
q = ++cnt;
lf[q] = l, rf[q] = r;
ls[q] = L, rs[q] = R;
sz[q] = 1;
Insert(1, 1, n, k, cnt);
return;
}
if (Equal(q, l, r))
{
Insert(1, 1, n, k, q);
return;
}
if (Less(q, l, r))
Add(rc[q], l, r, (L + R) / 2, R, k);
else
Add(lc[q], l, r, L, (L + R) / 2, k);
Pushup(q);
}
void Check(int &q, int l, int r)
{
if (sz[q] * lim < sz[lc[q]] || sz[q] * lim < sz[rc[q]])
{
Maintain(q);
return;
}
if (Equal(q, l, r))
return;
if (Less(q, l, r))
Check(rc[q], l, r);
else
Check(lc[q], l, r);
}
}T;
struct data
{
int plc, num;
data(){}
data(int a, int b){plc = a, num = b;}
double Val(){return (T.ls[num] + T.rs[num])/ 2;}
data operator + (data b)
{
if (num == 1 && b.num == 1)
return plc < b.plc ? *this : b;
if (num == 1)
return b;
if (b.num == 1)
return *this;
if (equal(Val(), b.Val()))
return plc < b.plc ? *this : b;
if (Val() > b.Val())
return *this;
return b;
}
};
struct SGT
{
data a[N << 2];
}S;
void Insert(int q, int l, int r, int k, int a)
{
if (l == k && r == k)
{
S.a[q] = data(k, a);
return;
}
int mid = (l + r) >> 1;
if (k <= mid)
Insert(q << 1, l, mid, k, a);
else
Insert(q << 1 | 1, mid + 1, r, k, a);
S.a[q] = S.a[q << 1] + S.a[q << 1 | 1];
}
data Query(int q, int l, int r, int L, int R)
{
if (l == L && r == R)
return S.a[q];
int mid = (l + r) >> 1;
if (R <= mid)
return Query(q << 1, l, mid, L, R);
if (L > mid)
return Query(q << 1 | 1, mid + 1, r, L, R);
return Query(q << 1, l, mid, L, mid) + Query(q << 1 | 1, mid + 1, r, mid + 1, R);
}
int main()
{
n = getint(), m = getint();
T.root = T.cnt = 1;
T.lf[1] = T.rf[1] = -1;
T.ls[1] = 0, T.rs[1] = 1;
for (int i = 1; i <= n; i++)
Insert(1, 1, n, i, 1);
for (int i = 1; i <= m; i++)
{
scanf("%s", opt);
if (opt[0] == 'Q')
{
int l = getint(), r = getint();
printf("%d\n", Query(1, 1, n, l, r).plc);
}
else
{
int l = getint(), r = getint(), k = getint();
l = Query(1, 1, n, l, l).num;
r = Query(1, 1, n, r, r).num;
T.Add(T.root, l, r, 0, 1, k);
T.Check(T.root, l, r);
}
}
}

「BZOJ3600」没有人的算术 替罪羊树+线段树的更多相关文章

  1. 【BZOJ3600】没有人的算术 - 替罪羊树+线段树

    题意: 题解: Orz vfleaking……真·神题 做法大概是先把题意中定义的“数”都赋一个实数权值,用平衡树来维护整个从大到小排序过的序列,再用线段树查询最值: 这样做为什么是对的?考虑插入一个 ...

  2. 【LibreOJ】#6396. 「THUPC2018」弗雷兹的玩具商店 / Toyshop 线段树+完全背包

    [题目]#6396. 「THUPC2018」弗雷兹的玩具商店 / Toyshop [题意]给定一个长度为n的物品序列,每个物品有价值.不超过m的重量.要求支持以下三种操作:1.物品价值区间加减,2.物 ...

  3. 「LuoguP3865」 【模板】ST表 (线段树

    题目背景 这是一道ST表经典题——静态区间最大值 请注意最大数据时限只有0.8s,数据强度不低,请务必保证你的每次查询复杂度为 O(1) 题目描述 给定一个长度为 N 的数列,和 M 次询问,求出每一 ...

  4. 「BZOJ1537」Aut – The Bus(变形Dp+线段树/树状数组 最优值维护)

    网格图给予我的第一反应就是一个状态 f[i][j] 表示走到第 (i,j) 这个位置的最大价值. 由于只能往下或往右走转移就变得显然了: f[i][j]=max{f[i-1][j], f[i][j-1 ...

  5. LOJ 3094 「BJOI2019」删数——角标偏移的线段树

    题目:https://loj.ac/problem/3094 弱化版是 AGC017C . 用线段树维护那个题里的序列即可. 对应关系大概是: 真实值的范围是 [ 1-m , n+m ] :考虑设偏移 ...

  6. 「BZOJ3065」带插入区间第K小值 替罪羊树×线段树

    题目描述 从前有\(n\)只跳蚤排成一行做早操,每只跳蚤都有自己的一个弹跳力\(a_i\).跳蚤国王看着这些跳蚤国欣欣向荣的情景,感到非常高兴.这时跳蚤国王决定理性愉悦一下,查询区间\(k\)小值.他 ...

  7. 「luogu3380」【模板】二逼平衡树(树套树)

    「luogu3380」[模板]二逼平衡树(树套树) 传送门 我写的树套树--线段树套平衡树. 线段树上的每一个节点都是一棵 \(\text{FHQ Treap}\) ,然后我们就可以根据平衡树的基本操 ...

  8. 【BZOJ3600】没有人的算术(替罪羊树+线段树)

    点此看题面 大致题意: 定义任意数对\(>0\),数对之间比大小先比第一位.后比第二位,一开始数列全为\(0\),要求你支持\(a_k=(a_x,a_y)\)和询问区间最大值所在位置两种操作. ...

  9. bzoj 3600 没有人的算术 - 替罪羊树 - 线段树

    题目都是图片,就不给了,就给链接好了 由于bzoj比较慢,就先给[vjudge传送门] 有兴趣的可以去逛bzoj[bzoj传送门] 题目大意 有n个数a[1],a[2],...,a[n],它们开始都是 ...

随机推荐

  1. codeforces 615E Hexagons (二分+找规律)

    E. Hexagons time limit per test 1 second memory limit per test 256 megabytes input standard input ou ...

  2. Nginx+ffmpeg的HLS开源服务器搭建配置及开发详

    本文概述: 至目前为止,HLS 是移动平台上非常重要并十分流行的流媒体传输协议.做移动平台的流媒体开发,不知道它不掌握它 ,真是一大遗憾.而HLS的平台搭建有一定的难度,本文针对对该方向有一定了解的朋 ...

  3. URAL1517Freedom of Choice(后缀数组)

    Background Before Albanian people could bear with the freedom of speech (this story is fully describ ...

  4. ACM学习历程—UESTC 1215 Secrete Master Plan(矩阵旋转)(2015CCPC A)

    题目链接:http://acm.uestc.edu.cn/#/problem/show/1215 题目大意就是问一个2*2的矩阵能否通过旋转得到另一个. 代码: #include <iostre ...

  5. 【算法总结】Manacher's Algorithm

    Manacher's Algorithm针对的是最长回文子串问题.对于此问题,最直接的方法是遍历每一个元素,遍历过程中以每一个字符为中心向两边扩展以寻找此字符为中心的最长回文子串.复杂度O(n2).M ...

  6. Python:str.ljust()、str.rjust()、str.center()函数

    str.ljust().str.rjust().str.center()函数 功能:调整字符串站位宽度,并确定字符串对齐方式: #可以用其它字符填充字符: #字符串长度 = 字符串个数(包含空格.标点 ...

  7. 三、使用maven创建scala工程(scala和java混一起)

    本文先叙述如何配置eclipse中maven+scala的开发环境,之后,叙述如何实现spark的本地运行.最后,成功运行scala编写的spark程序. 刚开始我的eclipse+maven环境是配 ...

  8. java基础知识(6)---抽象类与接口

    抽象类: abstract抽象:不具体,看不明白.抽象类表象体现.在不断抽取过程中,将共性内容中的方法声明抽取,但是方法不一样,没有抽取,这时抽取到的方法,并不具体,需要被指定关键字abstract所 ...

  9. File 类 操作实例

    File 操作 <介绍> 尽管java.io定义的大多数类是实行流式操作的,File类不是.它直接处理文件和文件系统.也就是说,File类没有指定信息怎样从文件读取或向文件存储:它描述了文 ...

  10. 八数码 Java实现

    参考http://blog.csdn.net/helloworld10086/article/details/41853389 package com.EightNumber.view; import ...