数据结构 Week 2 --- 平衡树
平衡树的算法很多,可分为有旋和无旋
无旋平衡树有:替罪羊树,fnq treap
有旋平衡树有:AVL树,Splay
还有很多其他的平衡树算法,不必掌握太多
主要掌握Splay和fhq treap,因为这两种可以支持区间操作
平衡树模板之Splay
核心思想:通过旋转使操作节点伸展到根节点
本来只要通过一两次旋转就能达到平衡了(AVL树),但是Splay应用了更多的旋转,使操作节点伸展到根节点
只通过单旋,操作节虽然能伸展到根节点,但是不能平衡高度
所以要通过双旋,使高度平衡
code:
#include<iostream> #include<algorithm> #include<cstdio> #include<random> #include<vector> using namespace std; const int MAXN = 1e6 + 1e5 + 7; struct NODE { int fa, son[2]; int val, siz; int cnt; }spl[MAXN]; int cnt = 0, root = 0; inline bool is_y(int x, int f) { return spl[f].son[1] == x; } void inline con(int x, int fa, int k) { spl[x].fa = fa; spl[fa].son[k] = x; } inline void update(int x) { spl[x].siz = spl[spl[x].son[0]].siz + spl[spl[x].son[1]].siz + spl[x].cnt; } inline void newnode(int &x, int fa, int val) { spl[x = ++cnt].siz = 1; spl[x].cnt = 1; spl[x].fa = fa; spl[x].val = val; } void rotate(int x,int &fa) { int ff = spl[fa].fa; int k = is_y(x, fa); int kk = is_y(fa, ff); con(spl[x].son[k ^ 1], fa, k); con(fa, x, k ^ 1); fa = x; con(fa, ff, kk); update(spl[fa].son[k ^ 1]), update(fa); } void splaying(int x, int& y) { while (x != y) { int f = spl[x].fa, ff = spl[f].fa; if (f == y) rotate(x, y); else if (ff == y) { is_y(x, f) == is_y(f, ff) ? rotate(f, y) : rotate(x, f); rotate(x, y); } else { is_y(x, f) == is_y(f, ff) ? rotate(f, ff) : rotate(x, f); rotate(x, ff); } } } void delnode(int x) { splaying(x, root); if (spl[x].cnt > 1) spl[x].cnt--; else if (spl[root].son[1]) { int p = spl[root].son[1]; while (spl[p].son[0]) p = spl[p].son[0]; splaying(p, spl[root].son[1]); con(spl[root].son[0], p, 0); root = p; spl[p].fa = 0; update(root); } else root = spl[x].son[0], spl[root].fa = 0; } void ins(int& now, int fa, int val) { if (!now) newnode(now, fa, val), splaying(now, root); else if (val < spl[now].val) ins(spl[now].son[0], now, val); else if (val > spl[now].val) ins(spl[now].son[1], now, val); else spl[now].cnt++, spl[now].siz++, splaying(now, root); } void del(int now, int val) { if (val == spl[now].val) delnode(now); else if (val < spl[now].val) del(spl[now].son[0], val); else del(spl[now].son[1], val); } int getrank(int val) { int now = root, rank = 1; while (now) { if (val == spl[now].val) { rank += spl[spl[now].son[0]].siz; splaying(now, root); break; } if (val < spl[now].val) now = spl[now].son[0]; else { rank += spl[spl[now].son[0]].siz + spl[now].cnt; now = spl[now].son[1]; } } return rank; } int getnum(int rank) { int now = root; while (now) { int lsize = spl[spl[now].son[0]].siz; if (lsize + 1 <= rank && rank <= lsize + spl[now].cnt) { splaying(now, root); break; } if (rank <= lsize) now = spl[now].son[0]; else { rank -= lsize + spl[now].cnt; now = spl[now].son[1]; } } return spl[now].val; }
平衡树模板之fhq treap
核心思想:Treap + 随机化,Treap可以实现按某一个值分裂成两个子树,然后再合并
二叉搜索树对所有子树满足:左子树的所有值小于根节点,右子树的所有值大于根节点
堆对所有子树满足:左子树和右子树都大于/小于根节点
显然一棵树不能同时满足这两个性质,如果每个节点只有一个值的话
但是如果每个节点有两个值,那么就能满足其中一个值满足二叉搜索树的性质,另一个值满足堆的性质了
这样把二叉搜索树和堆叠在一起,便是Treap
fhq Treap就是把本来每个节点只有一个值的二叉搜索树,再新增一个随机的第二个值,然后使第二个值满足堆的性质
这样随机化的操作,相当于随机的插入顺序,使得二叉搜索树大概率平衡
核心操作是按某个值分裂成两个子树,实际上就是把一些边改一改,有些边删掉,有些边加到哪里
code:
#include<iostream> #include<algorithm> #include<cstdio> #include<random> using namespace std; const int MAXN = 1e5 + 7; struct NODE { int l, r; int val, key; int size; }fhq[MAXN]; int cnt = 0, root = 0; std::mt19937 rnd(233); inline int newnode(int val) { fhq[++cnt].val = val; fhq[cnt].key = rnd(); fhq[cnt].size = 1; return cnt; } inline void update(int now) { fhq[now].size = fhq[fhq[now].l].size + fhq[fhq[now].r].size + 1; } void split(int now, int val, int& x, int& y) { if (!now) x = y = 0; else { if (fhq[now].val <= val) { x = now; split(fhq[now].r, val, fhq[now].r, y); } else { y = now; split(fhq[now].l, val, x, fhq[now].l); } update(now); } } int merge(int x, int y) { if (!x || !y)return x + y; if (fhq[x].key > fhq[y].key) { fhq[x].r = merge(fhq[x].r, y); update(x); return x; } else { fhq[y].l = merge(x, fhq[y].l); update(y); return y; } } int x, y, z; inline void ins(int val) { split(root, val, x, y); root = merge(merge(x, newnode(val)), y); } inline void del(int val) { split(root, val, x, z); split(x, val - 1, x, y); y = merge(fhq[y].l, fhq[y].r); root = merge(merge(x, y), z); } inline int getrank(int val) { split(root, val - 1, x, y); int ans = fhq[x].size + 1; root = merge(x, y); return ans; } inline int getnum(int rank) { int now = root; while (now) { if (fhq[fhq[now].l].size + 1 == rank) break; else if (fhq[fhq[now].l].size >= rank) now = fhq[now].l; else { rank -= fhq[fhq[now].l].size + 1; now = fhq[now].r; } } return fhq[now].val; } inline int pre(int val) { split(root, val - 1, x, y); int now = x; while (fhq[now].r) now = fhq[now].r; root = merge(x, y); return fhq[now].val; } inline int nxt(int val) { split(root, val, x, y); int now = y; while (fhq[now].l) now = fhq[now].l; root = merge(x, y); return fhq[now].val; }
文艺平衡树---平衡树实现区间反转
要把一段区间拎出来然后反转
怎么用平衡树实现?
一颗二叉搜索树,它的中序遍历的输出结果,一定是按权值的大小从小到大排好序的,不管这颗二叉搜索树是长怎么样的
如果要让它的一段区间反转,也就是这段区间要实现按权值从大到小的遍历
怎么做?
我们一开始先对初始数组从左往右给所有数一个次序值,按次序值从小到大输出就是初始的数组了
那么反转一段区间,就是把这段区间的所有次序值反过来
暴力O(n)的反转次序值肯定是不行的
所以就用懒标记
一个点上面有懒标记,就代表以这个点为根节点的子二叉搜索树,它的次序值是全部反转了的
假设我打了一个懒标记,再进行输出的时候,没打懒标记的点就正常遍历,打了懒标记的点就先下传懒标记,再正常遍历
要反转一段区间,可以这样做:在这段区间里选一个点,点的左边部分要反转,右边部分要反转,然后把点的左右两边互换
所以下传懒标记的操作,就是把懒标记下传到左右儿子,然后交换左右儿子
就可以神奇的实现这个O(log)的反转操作
code:
#include<iostream> #include<algorithm> #include<cstdio> using namespace std; const int MAXN = 1e5 + 7; struct NODE { int fa, son[2]; int val, siz; int cnt; int lazy; }spl[MAXN]; int cnt = 0, root = 0; int n, m, l, r; inline bool is_y(int x, int f) { return spl[f].son[1] == x; } void inline con(int x, int fa, int k) { spl[x].fa = fa; spl[fa].son[k] = x; } inline void update(int x) { spl[x].siz = spl[spl[x].son[0]].siz + spl[spl[x].son[1]].siz + spl[x].cnt; } inline void newnode(int& x, int fa, int val) { spl[x = ++cnt].siz = 1; spl[x].cnt = 1; spl[x].fa = fa; spl[x].val = val; } void pd(int now) { if (spl[now].lazy) { spl[spl[now].son[0]].lazy ^= 1; spl[spl[now].son[1]].lazy ^= 1; swap(spl[now].son[0], spl[now].son[1]); spl[now].lazy = 0; } } void rotate(int x, int& fa) { int ff = spl[fa].fa; int k = is_y(x, fa); int kk = is_y(fa, ff); con(spl[x].son[k ^ 1], fa, k); con(fa, x, k ^ 1); fa = x; con(fa, ff, kk); update(spl[fa].son[k ^ 1]), update(fa); } void splaying(int x, int& y) { while (x != y) { int f = spl[x].fa, ff = spl[f].fa; if (f == y) rotate(x, y); else if (ff == y) { is_y(x, f) == is_y(f, ff) ? rotate(f, y) : rotate(x, f); rotate(x, y); } else { is_y(x, f) == is_y(f, ff) ? rotate(f, ff) : rotate(x, f); rotate(x, ff); } } } void ins(int& now, int fa, int val) { if (!now) newnode(now, fa, val), splaying(now, root); else if (val < spl[now].val) ins(spl[now].son[0], now, val); else if (val > spl[now].val) ins(spl[now].son[1], now, val); else spl[now].cnt++, spl[now].siz++, splaying(now, root); } int getnode(int rank) {//找到排名为rank的点的编号 int now = root; while (now) { pd(now); int lsize = spl[spl[now].son[0]].siz; if (lsize + 1 <= rank && rank <= lsize + spl[now].cnt) { splaying(now, root); break; } if (rank <= lsize) now = spl[now].son[0]; else { rank -= lsize + spl[now].cnt; now = spl[now].son[1]; } } return now; } void work(int l, int r) { int nl = getnode(l), nr = getnode(r + 2); splaying(nl, root); splaying(nr, spl[nl].son[1]); spl[spl[nr].son[0]].lazy ^= 1; } void print(int now) { if (!now) return; pd(now); if (spl[now].son[0]) print(spl[now].son[0]); if (spl[now].val > 0 && spl[now].val < n + 1) printf("%d ", spl[now].val); if (spl[now].son[1]) print(spl[now].son[1]); } int main() { cin >> n >> m; for (int i = 1; i <= n + 2; i++) {//1是0号,2到n+1是1到n号,n+2是n+1号 ins(root, 0, i - 1); } while (m--) { scanf("%d%d", &l, &r); work(l, r); } print(root); printf("\n"); return 0; }
二逼平衡树之线段树套平衡树
当我学了动态主席树(其实不是主席树了,而是树状数组套权值线段树,或者线段树套权值线段树也行)后,我做到一个数据结构题(CCPC长春B题)的时候很高兴地用了这个模板,然后惨MLE了几十发QAQ
一直以来很少考虑过空间问题
总是以为开几十个线段树都没什么大不了的。。
ICPC昆明M题我开了60个long long的线段树,没想到动态开点的写法爆了1G的内存。。从来没想过MLE的问题
经历了这两题,发现深入数据结构题后,需要考虑省空间这个问题了
这就一下子变难了很多,不仅要考虑时间,也要仔细考虑空间了
树状数组套权值线段树,其空间复杂度是O(nlognlogn),大多时候查询的时间复杂度是O(nlognlogn)
线段树套平衡树,其空间复杂度是O(nlogn),大多时候查询的时间复杂度是O(nlognlogn)
可以发现,第二种套法比第一种套法优一个log的空间复杂度
对于本题(二逼平衡树)而言,第二种套法的时间复杂度为O(nlognlognlogn),比第一种套法的时间复杂度多了一个log
两种套法都能AC
code:(线段树套平衡树)
#include<iostream> #include<algorithm> #include<cstdio> using namespace std; const int MAXN = 1e5 + 7; const int INF = 1e9 + 7; struct NODE { int l, r, mid; int root; }tree[MAXN * 4]; int a[MAXN]; /////////////////////////// //平衡树 struct SPL { int fa, son[2]; int val, siz, cnt; }spl[MAXN * 40]; int n, m, op, l, r, pos, k; int cnt = 0; inline bool is_y(int x, int fa) { return spl[fa].son[1] == x; } inline void con(int x, int fa, int k) { spl[x].fa = fa; spl[fa].son[k] = x; } inline void newnode(int& now, int fa, int val) { spl[now = ++cnt].cnt = 1; spl[now].siz = 1; spl[now].val = val; spl[now].fa = fa; } inline void update(int now) { spl[now].siz = spl[spl[now].son[0]].siz + spl[spl[now].son[1]].siz + spl[now].cnt; } inline void rotate(int x, int& y) { int ff = spl[y].fa; int k = is_y(x, y), kk = is_y(y, ff); con(spl[x].son[k ^ 1], y, k); con(y, x, k ^ 1); y = x; con(y, ff, kk); update(spl[y].son[k ^ 1]); update(y); } void splaying(int x, int& y) { while (x != y) { int f = spl[x].fa, ff = spl[f].fa; if (f == y) rotate(x, y); else if (ff == y) { is_y(x, f) == is_y(f, y) ? rotate(f, y) : rotate(x, f); rotate(x, y); } else { is_y(x, f) == is_y(f, ff) ? rotate(f, ff) : rotate(x, f); rotate(x, ff); } } } void delnode(int now, int& root) { splaying(now, root); if (spl[now].cnt > 1) spl[now].cnt--, spl[now].siz--; else if (spl[root].son[1]) { int p = spl[root].son[1]; while (spl[p].son[0]) p = spl[p].son[0]; splaying(p, spl[root].son[1]); con(spl[root].son[0], p, 0); spl[p].fa = 0; root = p; update(root); } else root = spl[now].son[0], spl[root].fa = 0; } void ins(int& now, int& root, int fa, int val) { if (!now) newnode(now, fa, val), splaying(now, root); else if (val < spl[now].val) ins(spl[now].son[0], root, now, val); else if (val > spl[now].val) ins(spl[now].son[1], root, now, val); else spl[now].cnt++, spl[now].siz++, splaying(now, root); } void del(int now, int& root, int val) { if (spl[now].val == val) delnode(now, root); else if (val < spl[now].val) del(spl[now].son[0], root, val); else del(spl[now].son[1], root, val); } int getrank(int& root, int val) { int now = root, rank = 1; while (now) { if (val == spl[now].val) { rank += spl[spl[now].son[0]].siz; splaying(now, root); break; } if (val < spl[now].val)now = spl[now].son[0]; else { rank += spl[spl[now].son[0]].siz + spl[now].cnt; now = spl[now].son[1]; } } return rank; } int getnum(int& root, int rank) { int now = root; while (now) { int lsize = spl[spl[now].son[0]].siz; if (lsize + 1 <= rank && rank <= lsize + spl[now].cnt) { splaying(now, root); break; } if (rank <= lsize) now = spl[now].son[0]; else { rank -= lsize + spl[now].cnt; now = spl[now].son[1]; } } return spl[now].val; } int prenum(int& root, int val) { return getnum(root, getrank(root, val) - 1); } int nxtnum(int& root, int val) { return getnum(root, getrank(root, val + 1)); } /////////////// void build(int pos, int l, int r) {//建树建根 tree[pos].l = l; tree[pos].r = r; tree[pos].mid = l + r >> 1; tree[pos].root = ++cnt; if (l == r) return; int mid = l + r >> 1; build(pos << 1, l, mid); build(pos << 1 | 1, mid + 1, r); } void SEG_ins(int pos, int p, int val) {//插入 ins(tree[pos].root, tree[pos].root, 0, val); if (tree[pos].l == tree[pos].r) return; int mid = tree[pos].mid; if (p <= mid) SEG_ins(pos << 1, p, val); else SEG_ins(pos << 1 | 1, p, val); } void SEG_del(int pos, int p, int val) {//删除 del(tree[pos].root, tree[pos].root, val); if (tree[pos].l == tree[pos].r) return; int mid = tree[pos].mid; if (p <= mid) SEG_del(pos << 1, p, val); else SEG_del(pos << 1 | 1, p, val); } int SEG_rank(int pos, int l, int r, int val) {//比val小的数的个数 if (tree[pos].l == l && tree[pos].r == r) { return getrank(tree[pos].root, val) - 1; } int mid = tree[pos].mid; if (r <= mid) return SEG_rank(pos << 1, l, r, val); else if (l > mid) return SEG_rank(pos << 1 | 1, l, r, val); else return SEG_rank(pos << 1, l, mid, val) + SEG_rank(pos << 1 | 1, mid + 1, r, val); } int SEG_pre(int pos, int l, int r, int val) { if (tree[pos].l == l && tree[pos].r == r) { return prenum(tree[pos].root, val); } int mid = tree[pos].mid; if (r <= mid) return SEG_pre(pos << 1, l, r, val); else if (l > mid) return SEG_pre(pos << 1 | 1, l, r, val); else return max(SEG_pre(pos << 1, l, mid, val), SEG_pre(pos << 1 | 1, mid + 1, r, val)); } int SEG_nxt(int pos, int l, int r, int val) { if (tree[pos].l == l && tree[pos].r == r) { int res = nxtnum(tree[pos].root, val); if (!res) res = INF; return res; } int mid = tree[pos].mid; if (r <= mid) return SEG_nxt(pos << 1, l, r, val); else if (l > mid) return SEG_nxt(pos << 1 | 1, l, r, val); else return min(SEG_nxt(pos << 1, l, mid, val), SEG_nxt(pos << 1 | 1, mid + 1, r, val)); } int main() { cin >> n >> m; build(1, 1, n); for (int i = 1; i <= n; i++) scanf("%d", &a[i]), SEG_ins(1, i, a[i]); while (m--) { scanf("%d", &op); if (op == 3) { scanf("%d%d", &pos, &k); SEG_del(1, pos, a[pos]); a[pos] = k; SEG_ins(1, pos, a[pos]); } else { scanf("%d%d%d", &l, &r, &k); if (op == 1) printf("%d\n", SEG_rank(1, l, r, k) + 1); if (op == 2) { int al = 0, ar = INF; while (al < ar) {//找到排名大于k的最小的数 int mid = al + ar >> 1; int rk = SEG_rank(1, l, r, mid) + 1; if (rk < k + 1) al = mid + 1; else ar = mid; } printf("%d\n", SEG_pre(1, l, r, al)); } if (op == 4) { int val = SEG_pre(1, l, r, k); if (val == 0 || val == k) printf("-2147483647\n"); else printf("%d\n", val); } if (op == 5) { int val = SEG_nxt(1, l, r, k); if (val == 0 || val == k || val == INF) printf("2147483647\n"); else printf("%d\n", val); } } } return 0; }
可持久化平衡树
这个就需要用fhq treap了
待补。。。