[湖北省队互测2014] 没有人的算术 (非题解)

只有跳蚤的算术。
题面题解在此
正解用了替罪羊树哎

推荐阅读
洛谷日报:替罪羊树
《­重量平衡树和后缀平衡树在信息学奥赛中的应用》—— 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;
}
原文地址:https://www.cnblogs.com/tztqwq/p/13150698.html