只有跳蚤的算术。
题面题解在此
正解用了替罪羊树哎
推荐阅读
洛谷日报:替罪羊树
《重量平衡树和后缀平衡树在信息学奥赛中的应用》—— clj
初步思路
题面就是赋值和区间最大值。
区间最大值明显用线段树维护, 重要的是如何比较两个 “数” 的大小。
可以直接用平衡树维护全序集(平衡树就是用来维护全序集的东西), 这样的话比较两个“数”大小时就可以通过比较其在平衡树里的排名来比较大小。
注意到每个新 “数” 都由平衡树里的老 “数” 合成, 所以总是可以方便地比较老 “数” 与 新 “数” 的大小。
具体地说, 每当 (a[k]) 被赋值为 (new = (a[l], a[r])) 时, 将 (new) 插入到平衡树中, 插入过程中的大小比较可以套一个平衡树里的查找((a[l]、a[r]) 的排名预先找出来, 与当前节点比较的时候直接查找当前节点 ((l,r)) 的排名), 一次插入的总复杂度为 (O(log^2n)) 。
由于在线段树里修改也要比较大小,所以在线段树里修改的复杂度也是 (log^2) 的。
全序集的同构?
之前用平衡树维护的 “数” 的全序集 (S) 中, 若有 (x,y in S) 且 (x < y),则 (rank(x) < rank(y)), 反过来也成立。
如果定义一个函数 (infty(x)) 使得若有 若有 (x,y in S) 且 (x < y), 则必有 (infty(x) < infty(y)) 且反过来也成立, 那么比较两个“数”大小时, 比较 (rank) 的过程就可以换为比较 (infty), 那么就不用每次都在插入里套一个查找, 复杂度就变为 (O(log n * 比较infty的复杂度)) 。
比较 (infty) 的复杂度自然最好是 (O(1))。
满分做法
思路来自近代神犇 ( ext{vfleaking})。
对于一个平衡树中的节点, 给它一个开区间 ((l,r)), 区间中点 (mid = frac{l+r}2), 则它的左儿子的区间为 ((l,mid)), 右节点的区间为 ((mid,r))。
对每个节点定义一个函数 (infty), 若一个节点 (x) 的区间为 ((l,r)) 则
(infty(x) = frac{l+r}2)。
可以简单地证明通过 (infty) 比较平衡树中的两个“数”的大小与通过 (rank) 比较平衡树中两个“数”的大小, 结果是等价的。
然后就可以 (O(1)) 比较两个 “数” 的大小了。
但是有一个问题, 就是 (infty) 是自上到下计算的, 如果使用旋转平衡树的话, 维护 (infty) 会很麻烦。
所以这时候就要用替罪羊树啦, 无旋, 好写,复杂度好证。
代码设计
思路来自 (Marser)。
- 主体是结构体 “数”, 有标号, 每个 “数” 要记录自己的 (infty) 值 和 组成其的两个数的标号。
- 平衡树直接维护 “数”。
- 线段树维护一个序列, 序列中每个数都是某个 “数” 的标号。
以下代码(Luogu) 数据能过。(懒得自己测原数据了qwq)
#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 15;
int n,m;
int pos[N];
const double al = 0.7;
int top;
//double tl, tr;
int tot;
double val[N];
struct node {
int l,r;
bool operator<(node p) {
return val[l]==val[p.l]?val[r]<val[p.r]:val[l]<val[p.l];
}
bool operator==(node p) {
return l==p.l && r==p.r;
}
} nodes[N];
int rt, siz[N], ch[N][2];
inline void ud(int x) {
siz[x]=1+siz[ch[x][0]]+siz[ch[x][1]];
}
int len,arr[N];
void pia(int x) {
if(!x) return;
pia(ch[x][0]);
arr[++len]=x;
pia(ch[x][1]);
}
void build(int &x,int l,int r,double L,double R) {
if(l>r) return (void)(x=0);
int mid=(l+r)>>1;
double MID=(L+R)/2;
x=arr[mid];
val[x]=MID;
siz[x]=1;
build(ch[x][0],l,mid-1,L,MID);
build(ch[x][1],mid+1,r,MID,R);
ud(x);
}
void rebuild(int &x,double L,double R) {
len=0;
pia(x);
build(x,1,len,L,R);
top=0;
}
int ins(int &x,double L,double R,node a) {
double MID=(L+R)/2;
if(!x) {
x=++tot;
siz[x]=1;
val[x]=MID;
nodes[x]=a;
return x;
}
if(nodes[x]==a) return x;
int res = 0;
if(a<nodes[x]) res=ins(ch[x][0],L,MID,a);
else res=ins(ch[x][1],MID,R,a);
ud(x);
// if(max(siz[ch[x][0]], siz[ch[x][1]]) > siz[x] * al) top=x, tl=L, tr=R;
if(max(siz[ch[x][0]],siz[ch[x][1]])<siz[x]*al) {
if(top) {
if(ch[x][0]==top) rebuild(ch[x][0],L,MID);
else rebuild(ch[x][1],MID,R);
top=0;
}
} else top=x;
return res;
}
namespace seg {
int s[N];
#define ls (u<<1)
#define rs (u<<1|1)
void upd(int u,int l,int r,int k) {
if(l==r) return (void)(s[u]=l);
int mid=(l+r)>>1;
if(k<=mid) upd(ls,l,mid,k);
else upd(rs,mid+1,r,k);
s[u] = (val[pos[s[ls]]]<val[pos[s[rs]]]) ? s[rs] : s[ls];
}
int ques(int u,int l,int r,int x,int y) {
if(x<=l&&r<=y) return s[u];
int mid=(l+r)>>1;
if(y<=mid) return ques(ls,l,mid,x,y);
if(x>mid) return ques(rs,mid+1,r,x,y);
int pl=ques(ls,l,mid,x,y), pr=ques(rs,mid+1,r,x,y);
if(val[pos[pl]]<val[pos[pr]]) return pr;
else return pl;
}
#undef ls
#undef rs
}
inline void change(int k) {
seg::upd(1,1,n,k);
}
inline int ques(int l,int r) {
return seg::ques(1,1,n,l,r);
}
int main() {
scanf("%d%d", &n, &m);
val[0]=-1;
ins(rt,0,1,(node) {
0,0
});
for(int i=1; i<=n; ++i) pos[i]=tot;
for(int i=1; i<=n; ++i) change(i);
char s[3];
int l,r,k;
while(m--) {
scanf("%s", s);
if(s[0]=='C') {
scanf("%d%d%d", &l,&r,&k);
pos[k] = ins(rt, 0, 1, (node) {
pos[l],pos[r]
});
//if(SGT::top)SGT::rebuild(root,0,1),SGT::top=0;
if(top) rebuild(rt,0,1);
change(k);
} else {
scanf("%d%d", &l,&r);
printf("%d
", ques(l,r));
}
}
return 0;
}