平衡树学习笔记+做题记录

发布时间 2023-04-05 23:09:54作者: SF71-H

〇、二叉搜索树(BST, Binary Search Tree)

BST 满足以下性质:

对于节点 \(u\),他最多只有两个儿子,以左儿子为根的子树中的所有元素小于 \(a_u\),以右儿子为根的子树中的所有元素大于 \(a_u\)

可以一直向左儿子跳找到最小值,一直向右儿子跳找到最大值。

BST 的操作作为前置知识,不会可以右转去 OI-Wiki:https://oi-wiki.org/ds/bst/

一、替罪羊树

如果 BST 退化成一条链,查找的时间复杂度就从 \(O(\log n)\) 退化成 \(O(n)\),如果是一道 DS 题就会被卡到 \(O(nm)\),显然是我们不能接受的。

怎么办?很简单,如果子树的比例过于不均衡(比如子树的大小 \(siz_{ls_u} > \alpha \times siz_u\))就当成替罪羊重构就行了。(这也是替罪羊树名字的由来)

关于常数 \(\alpha\) 的取值:\(\alpha\) 越大,重构次数越,单次查询复杂度越,所以一般取 \(\alpha = 0.7 \sim 0.75\)

贴一下代码:

const int N = 1e5 + 5; 
struct scapegoat_tree {
	double alpha = 0.75;
	struct node {
		int L, R, val, siz, valid;
		bool del;
		inline void new_node (int u) {
			L = R = 0;
			val = u;
			siz = valid = 1;
			del = false;
		}
	} tr[N << 2];
	int tot = 0, root = 0;
	inline bool bad (int u) {
		double __std = alpha * tr[u].siz;
		int ls = tr[u].L;
		int rs = tr[u].R;
		if ((double) (tr[ls].siz) > __std) return true;
		if ((double) (tr[rs].siz) > __std) return true;
		return false;
	}
	inline void update (int u) {
		int ls = tr[u].L;
		int rs = tr[u].R;
		tr[u].siz = tr[ls].siz + tr[rs].siz + !tr[u].del;
		tr[u].valid = tr[ls].valid + tr[rs].valid + !tr[u].del;
	}
	inline void dfs (int u, vector < int > &st) {
		if (!u) return ;
		dfs (tr[u].L, st);
		if (!tr[u].del) st.push_back (u);
		dfs (tr[u].R, st);
		return ;
	}
	inline int build (vector < int > &st, int l, int r) {
		if (l >= r) return 0;
		int mid = l + r >> 1;
		int u = st[mid];
		tr[u].L = build (st, l, mid);
		tr[u].R = build (st, mid + 1, r);
		update (u);
		return u;
	}
	inline void rebuild (int &u) {
		vector < int > st;
		dfs (u, st);
		u = build (st, 0, st.size ());
	}
	inline void insert_element (int val, int &u) {
		if (!u) {
			u = ++ tot;
			tr[u].new_node (val);
			return ; 
		}
		tr[u].siz ++;
		tr[u].valid ++;
		if (val >= tr[u].val) insert_element (val, tr[u].R);
		else insert_element (val, tr[u].L);
		if (bad (u)) rebuild (u);
	}
	inline int rank (int u, int x) {
		int ans = 1;
		while (u) {
			if (tr[u].val >= x) u = tr[u].L;
			else {
				ans += tr[tr[u].L].valid + !tr[u].del;
				u = tr[u].R;
			}
		}
		return ans;
	}
	inline int kth (int rt, int x) {
		while (rt) {
			if (!tr[rt].del && tr[tr[rt].L].valid + 1 == x) return tr[rt].val;
			if (tr[tr[rt].L].valid >= x) rt = tr[rt].L;
			else {
				x -= tr[tr[rt].L].valid + !tr[rt].del;
				rt = tr[rt].R;
			}
		}
	}
	inline void del (int u, int rnk) {
		if (!tr[u].del && rnk == tr[tr[u].L].valid + 1) {
			tr[u].del = 1;
			tr[u].valid --;
			return ;
		}
		tr[u].valid --;
		if (rnk <= tr[tr[u].L].valid + !tr[u].del) del (tr[u].L, rnk);
		else del (tr[u].R, rnk - tr[tr[u].L].valid - !tr[u].del);
	}
} sgt;

讲一下常见的六种操作:

  • 插入操作
insert_element (x, root);
  • 删除操作
del (root, rank (root, x));
  • 查询排名
int result = rank (root, x);
  • 查询第 k 大
int result = kth (root, k);
  • 查询 x 的前驱
int rk = rank (root, x) - 1;
int result = kth (root, rk);
  • 查询 x 的后继
int rk = rank (root, x + 1);
int result = kth (root, rk);

二、Treap

\[\texttt{Treap = Tree + Heap} \]

将树的结构利用随机优先级的方式重新构建即可降低复杂度。

struct treap {
	struct node {int ls, rs, val, pri, cnt, siz;} tr[N];
	int root, tot;
	inline int create (int w) {
		tr[++ tot].val = w;
		tr[tot].pri = randomly (1, V), tr[tot].cnt = tr[tot].siz = 1;
		return tot;
	}
	inline void update (int u) {tr[u].siz = tr[u].cnt + tr[tr[u].ls].siz + tr[tr[u].rs].siz;}
	inline int rank (int u, int w) {
		if (!u) return 0;
		if (w == tr[u].val) return tr[tr[u].ls].siz + 1;
		if (w < tr[u].val) return rank (tr[u].ls, w);
		return rank (tr[u].rs, w) + tr[tr[u].ls].siz + tr[u].cnt;
	}
	inline int kth (int u, int k) {
		if (u == 0) return V;
		if (tr[tr[u].ls].siz >= k) return kth (tr[u].ls, k);
		if (tr[tr[u].ls].siz + tr[u].cnt >= k) return tr[u].val;
		return kth (tr[u].rs, k - tr[tr[u].ls].siz - tr[u].cnt);
	}
	inline void zig (int &u) {
		int v = tr[u].ls;
		tr[u].ls = tr[v].rs, tr[v].rs = u, u = v;
		update (tr[u].rs), update (u);
	}
	inline void zag (int &u) {
		int v = tr[u].rs;
		tr[u].rs = tr[v].ls, tr[v].ls = u, u = v;
		update (tr[u].ls), update (u);
	}
	inline void build () {
		create (-V), create (V);
		root = 1, tr[root].rs = 2;
		if (tr[1].pri < tr[2].pri) zag (root);
	}
	inline void ins (int &u, int w) {
		if (u == 0) {u = create (w); return ;}
		if (w == tr[u].val) {tr[u].cnt ++, update (u); return ;}
		if (w < tr[u].val) {ins (tr[u].ls, w); if (tr[tr[u].ls].pri > tr[u].pri) zig (u);}
		if (w > tr[u].val) {ins (tr[u].rs, w); if (tr[tr[u].rs].pri > tr[u].pri) zag (u);}
		update (u);
	}
	inline int pre (int u, int w) {
		if (!u) return -V;
		if (w <= tr[u].val) return pre (tr[u].ls, w);
		int res = pre (tr[u].rs, w);
		return res == -V ? tr[u].val : res;
	}
	inline int nxt (int u, int w) {
		if (!u) return V;
		if (w >= tr[u].val) return nxt (tr[u].rs, w);
		int res = nxt (tr[u].ls, w);
		return res == V ? tr[u].val : res;
	}
	inline void del (int &u, int v) {
		if (!u) {return ;}
		if (v == tr[u].val) {
			if (tr[u].cnt > 1) {tr[u].cnt --, update (u); return ;}
			if (tr[u].ls || tr[u].rs) {
				if (tr[u].rs == 0 || tr[tr[u].ls].pri > tr[tr[u].rs].pri) {zig (u), del (tr[u].rs, v);}
				else {zag (u), del (tr[u].ls, v);}
				update (u);
			}
			else u = 0; return ;
		}
		if (v < tr[u].val) {del (tr[u].ls, v);} else {del (tr[u].rs, v);}
		update (u);
	}
} tre;