【题解】BZOJ 3600: 没有人的算术——替罪羊树、线段树
题意
具体的自己去上面看吧...反正不是权限题。
简单来说,就是定义了一类新的数,每个数是0或者为 \((x_L, x_R)\) ,同时定义比较大小的方式为:非零数大于零,否则按字典序比较(先比较 \(x_L\) ,相等就比较 \(x_R\) ,递归定义) 。
一开始序列A中元素都是0,然后支持两种操作:
- C l r k: 将A[k]赋值为(A[l],A[r])。
- Q l r: 询问A[l],A[l+1]...A[r]中的最大值的编号,若有多个最大值,输出最小的编号。
简要做法
我们尝试将问题分解。
首先,如果数组元素不是题目定义的乱七八糟的数,而是普通的整数,那么就是简单的线段树操作了。
观察发现,题目中的“小于”满足传递性,所以可以给出一个全序关系。
考虑给每个元素一个唯一的编号,编号满足元素之间的大小关系。这样就可以用线段树维护了。
问题来了,如何确定这个编号呢?
一个直接的想法是:维护一个有序表,存储所有出现过的元素,以及它的“左数”和“右数”在表中对应的位置。
每当产生一个新数 \((x_L, x_R)\) ,由于所有元素的“左数”和“右数”都一定在表中,我们一定可以找到一个位置,将这个新数插入。然后更新表中的信息。
这显然可以使用平衡树来实现。
但是假如按照题意递归地比较“左数”和“右数”的元素本身,时间复杂度就难以保证,怎么办呢?
考虑给每个元素分配一个不连续的编号。像线段树一样,每个节点对应一个实数区间,它的编号为区间的中点。显然,这样产生的编号满足题目中的全序关系,不会冲突。
于是,在每个节点储存它的“左数”和“右数”的指针,这样比较两个元素时,分别比较“左数”和“右数”的编号的大小即可。
问题似乎解决了。但是还有一个小问题:假如使用了基于旋转的平衡树,每次旋转会破坏区间的性质,难以维护。
没有关系,我们还有不基于旋转的平衡树,简单又粗暴好写又高效的基于暴力重构的替罪羊树是非常优秀的一种(非旋转treap常数好大)。
细节
- 编号可以不用double,而用unsigned long long,主要是出于速度的考虑。
- 开fread可以跑得更快
- 某思博选手因为忘了pushup而WA了3次,惨呐

代码
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
const int MAXN=500005, MAXB=2e7;
const double al=0.66;
const ll MX=1ULL<<60;
char BUF[MAXB], *cp=BUF;
void rd(int &x){
x=0;
while(*cp<'0'||'9'<*cp)cp++;
while('0'<=*cp&&*cp<='9')x=x*10+*cp++-'0';
}
char rc(){
while(*cp<'A'||'Z'<*cp)cp++;
return *cp++;
}
int N, M, L, R, tot=1, ok, top;
struct Node{
Node *lc, *rc, *l, *r;
ll a,b,f;
int sz;
void up(){sz=1+lc->sz+rc->sz;}
}nd[MAXN], *root, *st[MAXN], *A[MAXN];
void init(){
nd[0].lc=nd[0].rc=nd; nd->f=0;
nd[1].l=nd[1].r=nd[1].lc=nd[1].rc=nd;
nd[1].sz=1; nd[1].a=0; nd[1].b=MX; nd[1].f=MX>>1;
root=nd+1;
for(int i=0; i<=N; ++i) A[i]=nd+1;
}
inline int cmp(Node *l1, Node *r1, Node *l2, Node *r2){
if(l1->f==l2->f) return r1->f==r2->f?0:(r1->f<r2->f?-1:1);
return l1->f<l2->f?-1:1;
}
void dfs(Node *x){
if(x==nd) return;
dfs(x->lc); st[top++]=x; dfs(x->rc);
}
void bu(Node *&x, int l, int r, ll a, ll b){
if(l>r){x=nd; return;}
int mid=l+r>>1; x=st[mid];
ll f=a+b>>1;
x->a=a; x->b=b; x->f=f;
bu(x->lc,l,mid-1,a,f-1);
bu(x->rc,mid+1,r,f+1,b);
x->up();
}
void rebu(Node *&x){
top=0; dfs(x); bu(x,0,top-1,x->a,x->b);
}
void ins(Node *&x, Node *l, Node *r, ll a, ll b, int i){
ll f=a+b>>1;
if(x==nd){
x=&nd[++tot]; x->lc=x->rc=nd; x->sz=1;
x->l=l; x->r=r; x->a=a; x->b=b; x->f=f;
A[i]=x; return;
}
int t=cmp(l,r,x->l,x->r);
if(t==0){A[i]=x; return;}
else if(t<0){
ins(x->lc,l,r,a,f-1,i); x->up();
if(!ok&&x->lc->sz>=al*x->sz) ok=1,rebu(x);
}else{
ins(x->rc,l,r,f+1,b,i); x->up();
if(!ok&&x->rc->sz>=al*x->sz) ok=1,rebu(x);
}
}
inline void chkmx(int &x, int y){
ll a=A[x]->f, b=A[y]->f;
if(!x||a<b||a==b&&x>y)x=y;
}
struct Seg{
int mx[MAXN];
void pushup(int x){
chkmx(mx[x],mx[x<<1]);
chkmx(mx[x],mx[x<<1|1]);
}
void upd(int x, int l, int r, int k){
if(l==r){chkmx(mx[x],k); return;}
int mid=l+r>>1;
if(k<=mid) upd(x<<1,l,mid,k);
else upd(x<<1|1,mid+1,r,k);
pushup(x);
}
void bu(int x, int l, int r){
if(l==r){mx[x]=l; return;}
int mid=l+r>>1;
bu(x<<1,l,mid);
bu(x<<1|1,mid+1,r);
pushup(x);
}
int gmax(int x, int l, int r){
if(L<=l&&r<=R) return mx[x];
int mid=l+r>>1, ret=0;
if(L<=mid) chkmx(ret,gmax(x<<1,l,mid));
if(mid<R) chkmx(ret,gmax(x<<1|1,mid+1,r));
return ret;
}
}seg;
int main(){
fread(BUF, 1, MAXB, stdin);
rd(N),rd(M); init(); seg.bu(1,1,N);
while(M--){
if(rc()=='C'){
int a,b,c; rd(a),rd(b),rd(c); ok=0;
ins(root,A[a],A[b],0,MX,c);
seg.upd(1,1,N,c);
}else{
rd(L),rd(R);
printf("%d\n", seg.gmax(1,1,N));
}
}
return 0;
}
【题解】BZOJ 3600: 没有人的算术——替罪羊树、线段树的更多相关文章
- bzoj 3600 没有人的算术 - 替罪羊树 - 线段树
题目都是图片,就不给了,就给链接好了 由于bzoj比较慢,就先给[vjudge传送门] 有兴趣的可以去逛bzoj[bzoj传送门] 题目大意 有n个数a[1],a[2],...,a[n],它们开始都是 ...
- bzoj 3600: 没有人的算术
Description Solution 我们可以给每一个数钦定一个权值 , 这样就可以 \(O(1)\) 比较大小了. 考虑怎么确定权值: 用平衡树来维护 , 我们假设根节点管辖 \([1,2^{6 ...
- bzoj 3600 没有人的算术——二叉查找树动态标号
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3600 已知 l 和 r 的排名,想快速知道 k 的排名.那么建一个 BIT ,用已知的排名做 ...
- 「BZOJ3065」带插入区间第K小值 替罪羊树×线段树
题目描述 从前有\(n\)只跳蚤排成一行做早操,每只跳蚤都有自己的一个弹跳力\(a_i\).跳蚤国王看着这些跳蚤国欣欣向荣的情景,感到非常高兴.这时跳蚤国王决定理性愉悦一下,查询区间\(k\)小值.他 ...
- BZOJ.3307.雨天的尾巴(dsu on tree/线段树合并)
BZOJ 洛谷 \(dsu\ on\ tree\).(线段树合并的做法也挺显然不写了) 如果没写过\(dsu\)可以看这里. 对修改操作做一下差分放到对应点上,就成了求每个点子树内出现次数最多的颜色, ...
- 「BZOJ3600」没有人的算术 替罪羊树+线段树
题目描述 过长--不想发图也不想发文字,所以就发链接吧-- 没有人的算术 题解 \(orz\)神题一枚 我们考虑如果插入的数不是数对,而是普通的数,这就是一道傻题了--直接线段树一顿乱上就可以了. 于 ...
- 【BZOJ3600】没有人的算术 - 替罪羊树+线段树
题意: 题解: Orz vfleaking……真·神题 做法大概是先把题意中定义的“数”都赋一个实数权值,用平衡树来维护整个从大到小排序过的序列,再用线段树查询最值: 这样做为什么是对的?考虑插入一个 ...
- 【BZOJ3600】没有人的算术(替罪羊树+线段树)
点此看题面 大致题意: 定义任意数对\(>0\),数对之间比大小先比第一位.后比第二位,一开始数列全为\(0\),要求你支持\(a_k=(a_x,a_y)\)和询问区间最大值所在位置两种操作. ...
- BZOJ 5326 [JSOI2017]博弈 (模拟费用流、线段树)
题目链接 https://www.lydsy.com/JudgeOnline/problem.php?id=5326 题解 终于成为第8个A掉这题的人--orz tzw神仙早我6小时 本以为这东西常数 ...
随机推荐
- 单源最短路径spfa模板(pascal)洛谷P3371
题目描述 如题,给出一个有向图,请输出从某一点出发到所有点的最短路径长度. 输入输出格式 输入格式: 第一行包含三个整数N.M.S,分别表示点的个数.有向边的个数.出发点的编号. 接下来M行每行包含三 ...
- 【bzoj3697】采药人的路径 树的点分治
题目描述 给出一棵 $n$ 个点的树,每条边的边权为1或0.求有多少点对 $(i,j)$ ,使得:$i$ 到 $j$ 的简单路径上存在点 $k$ (异于 $i$ 和 $j$ ),使得 $i$ 到 $k ...
- 【bzoj4006】[JLOI2015]管道连接 斯坦纳树+状压dp
题目描述 给出一张 $n$ 个点 $m$ 条边的无向图和 $p$ 个特殊点,每个特殊点有一个颜色.要求选出若干条边,使得颜色相同的特殊点在同一个连通块内.输出最小边权和. 输入 第一行包含三个整数 n ...
- 题解 P1059 【明明的随机数】
不会其他排序的小金羊又来水题了 本题我的思路:堆排,速度不需要算很快,AC就可以... 注意:初学者不宜抄此代码(压行严重) code: #include <cstdio> #includ ...
- 51nod 1089最长回文子串V2 (manacher)
经典题 manacher是一种很神奇的算法, 算是动态规划的一种,不过利用的信息非常有效 #include <iostream> #include <cstdio> #incl ...
- UVA - 11997(巧妙的优先队列)
题意: 有k个整数数组,各包含k个元素,在每个数组中取一个元素加起来,可以得到kk个和,求这些和中最小的k个值 解析: 从简单的情况开始分析:经典方法,对原题没有思路,那么分析问题的简化版 这是对于两 ...
- Tajo--一个分布式数据仓库系统(分布式环境安装试用)
前面两篇介绍了一下tajo,下面就说一下安装和使用吧. 一.分布式安装 前提:hadoop2中的hdfs和yarn已经安装并运行正常. 1.下载source并build源码 $git clone ht ...
- 【ListBox】ListBox的相关操作
Winform中两个listbox的操作是平时比较常用的操作. 本次将以一个Winform实例来分享一下两个listbox的操作,包括:listbox添加项,项的上移下移等操作. 假设有两个listb ...
- 【loj2472】IIIDX
Portal --> loj2472 Solution 感觉是一道很有意思的贪心题啊ovo(想了一万个假做法系列==) 比较直观的想法是,既然一个数\(i\)只会对应一个\(\lfloor\fr ...
- SAS8.1安装步骤(附图)
安装前应当把系统时间更改到一九九几年. 1.在解压后的文件夹里找到 setup .exe 双击 开始安装 2.单击SAS System Setup 3.点击Next 4.选择 complete 并单击 ...